Skip to content

Commit 07a7db3

Browse files
committed
switch to fork-based FSNotify architecture to resolve setns() multithreading restrictions
1 parent ddf0db2 commit 07a7db3

File tree

4 files changed

+353
-113
lines changed

4 files changed

+353
-113
lines changed

Sources/Integration/Suite.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,22 @@ struct IntegrationSuite: AsyncParsableCommand {
9595
.appendingPathComponent(name)
9696
}
9797

98-
func bootstrap() async throws -> (rootfs: Containerization.Mount, vmm: VirtualMachineManager, image: Containerization.Image) {
99-
let reference = "ghcr.io/linuxcontainers/alpine:3.20"
98+
// Generate a simple hash from the image reference to ensure unique rootfs filenames
99+
private static func hashImageReference(_ reference: String) -> String {
100+
let hash =
101+
reference.data(using: .utf8)?.withUnsafeBytes { bytes in
102+
var hash: Int = 0
103+
for byte in bytes {
104+
hash = hash &* 31 &+ Int(byte)
105+
}
106+
return hash & 0x7FFF_FFFF
107+
} ?? 0
108+
return String(format: "%08x", hash)
109+
}
110+
111+
func bootstrap(reference: String = "ghcr.io/linuxcontainers/alpine:3.20") async throws -> (
112+
rootfs: Containerization.Mount, vmm: VirtualMachineManager, image: Containerization.Image
113+
) {
100114
let store = Self.imageStore
101115

102116
let initImage = try await store.getInitImage(reference: Self.initImage)
@@ -123,7 +137,8 @@ struct IntegrationSuite: AsyncParsableCommand {
123137
let platform = Platform(arch: "arm64", os: "linux", variant: "v8")
124138

125139
let fs: Containerization.Mount = try await {
126-
let fsPath = Self.testDir.appending(component: "rootfs.ext4")
140+
let imageHash = Self.hashImageReference(reference)
141+
let fsPath = Self.testDir.appending(component: "rootfs-\(imageHash).ext4")
127142
do {
128143
let unpacker = EXT4Unpacker(blockSizeInBytes: 2.gib())
129144
return try await unpacker.unpack(image, for: platform, at: fsPath)

Sources/Integration/VMTests.swift

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,18 @@ extension IntegrationSuite {
354354
func testFSNotifyEvents() async throws {
355355
let id = "test-fsnotify-events"
356356

357-
let bs = try await bootstrap()
357+
let bs = try await bootstrap(reference: "docker.io/library/node:18-alpine")
358358
let directory = try createFSNotifyTestDirectory()
359+
let inotifyBuffer: IntegrationSuite.BufferWriter = BufferWriter()
359360
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
360-
config.process.arguments = ["/bin/sh", "-c", "sleep 30"] // Keep container running
361+
config.process.arguments = [
362+
"node",
363+
"-e",
364+
"fs=require('fs');fs.watch(process.argv[1],(t,f)=>console.log(t,f))",
365+
"/mnt",
366+
]
367+
config.process.stdout = inotifyBuffer
368+
config.process.stderr = inotifyBuffer
361369
config.mounts.append(.share(source: directory.path, destination: "/mnt"))
362370
}
363371

@@ -368,8 +376,10 @@ extension IntegrationSuite {
368376
let connection = try await container.dialVsock(port: 1024) // Default vminitd port
369377
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
370378
let agent = Vminitd(connection: connection, group: group)
379+
try await Task.sleep(for: .seconds(1))
371380

372381
// Test 1: CREATE event on existing file
382+
print("Sending CREATE event...")
373383
let createResponse = try await agent.notifyFileSystemEvent(
374384
path: "/mnt/existing.txt",
375385
eventType: .create,
@@ -379,8 +389,10 @@ extension IntegrationSuite {
379389
guard createResponse.success else {
380390
throw IntegrationError.assert(msg: "CREATE event failed: \(createResponse.error)")
381391
}
392+
print("CREATE event succeeded")
382393

383394
// Test 2: MODIFY event on existing file
395+
print("Sending MODIFY event...")
384396
let modifyResponse = try await agent.notifyFileSystemEvent(
385397
path: "/mnt/existing.txt",
386398
eventType: .modify,
@@ -389,42 +401,23 @@ extension IntegrationSuite {
389401
guard modifyResponse.success else {
390402
throw IntegrationError.assert(msg: "MODIFY event failed: \(modifyResponse.error)")
391403
}
404+
print("MODIFY event succeeded")
392405

393-
// Test 3: Verify inotify events are actually generated using inotifywait
394-
let inotifyBuffer = BufferWriter()
395-
let inotifyProcess = try await container.exec("test-inotify") { config in
396-
// Install inotify-tools and monitor the mount point for events
397-
config.arguments = [
398-
"/bin/sh", "-c",
399-
"""
400-
apk add --no-cache inotify-tools > /dev/null 2>&1 && \
401-
timeout 5 inotifywait -m /mnt -e modify,create,delete --format '%e %f' 2>/dev/null || true
402-
""",
403-
]
404-
config.stdout = inotifyBuffer
405-
}
406-
407-
try await inotifyProcess.start()
408-
409-
// Wait for inotify to start monitoring, then send FSNotify events
410-
try await Task.sleep(for: .milliseconds(500))
411-
412-
// Send ONLY agent-driven events
413-
let _ = try await agent.notifyFileSystemEvent(
414-
path: "/mnt/existing.txt",
415-
eventType: .modify,
416-
containerID: id
417-
)
406+
// Wait for events to be processed
407+
print("Waiting for inotify events to be detected...")
408+
try await Task.sleep(for: .seconds(1))
418409

419-
let _ = try await inotifyProcess.wait()
420410
let inotifyOutput = String(data: inotifyBuffer.data, encoding: .utf8) ?? ""
411+
print("=== Final Container Output ===")
412+
print(inotifyOutput)
413+
print("=== End Container Output ===")
421414

422415
// Verify that inotify detected the modify event
423-
guard inotifyOutput.contains("MODIFY existing.txt") else {
424-
throw IntegrationError.assert(msg: "inotify did not detect FSNotify agent modify event. Output: '\(inotifyOutput)'")
416+
guard inotifyOutput.contains("change") && inotifyOutput.contains("existing.txt") else {
417+
throw IntegrationError.assert(msg: "inotify did not detect FSNotify agent events. Output: '\(inotifyOutput)'")
425418
}
426419

427-
// Test 4: DELETE event on non-existent file
420+
// Test 3: DELETE event on non-existent file
428421
let deleteResponse = try await agent.notifyFileSystemEvent(
429422
path: "/mnt/nonexistent.txt",
430423
eventType: .delete,

0 commit comments

Comments
 (0)