Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Swap to APIServer for all communications - #628

Merged
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang
Sep 20, 2025
Merged

Swap to APIServer for all communications#628
dcantah merged 1 commit into
apple:mainfrom
dcantah:move-to-api-server-for-everythang

Conversation

@dcantah

Copy link
Copy Markdown
Contributor

Today, we have a sort of odd model all things considered. We talk to the APIServer to do the initial container creation, which mostly all of the work is just registering the runtime helper with launchd. After this registration, almost all communication from a client is talking directly to that runtime helper. This has a couple rather annoying issues in that we now need a sort of event/notification channel that the helper will establish with the APIServer for errors/start/exited cases.

If instead of talking to the runtime helper directly, we instead took a detour through the APIServer, the APIServer is clued into exactly what order of operations is occurring. This makes "did starting the container fail? Okay we should clean up" scenarios much simpler, and it also simplifies the clients quite a bit as they don't need this split brained client model, everyone just talks to the APIServer. This change is in pursuit of that. I have reworked our clients, the ContainerService and some of our XPC types to accomplish it.

The biggest "contract" change is in the SandboxService. Today we have on the flag when we register any runtime helper that makes any xpc messages wake up the registered process. This isn't great in scenarios where the process have may crashed, or it exited normally and we're just trying to invoke an RPC on it. Today the helper would spawn again and try and answer our request. It'd be much nicer if we have a connection object that will become invalid if the process that vended it to us is gone. To accomplish this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints from this via only one handler exposed by the SandboxService (createEndpoint). From that point onwards all communication will be through the endpoint the service vended a client.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 4838bcf to e7e4dcbCompareSeptember 18, 2025 03:29
@dcantah
dcantah marked this pull request as ready for review September 18, 2025 03:34
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 3 times, most recently from 1a56c99 to 6a497e6CompareSeptember 18, 2025 07:25
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift Outdated
Comment threadSources/ContainerClient/ContainerEvents.swift
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 6a497e6 to 5d5e622CompareSeptember 18, 2025 18:43
Comment threadSources/ContainerClient/SandboxClient.swift
Comment threadSources/Services/ContainerNetworkService/NetworkClient.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift
Comment threadSources/ContainerClient/Core/ClientProcess.swift Outdated
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/ContainerClient/Core/ClientContainer.swift
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
jglogan
jglogan previously approved these changes Sep 19, 2025
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
Comment threadSources/Services/ContainerAPIService/Containers/ContainersService.swift Outdated
@jglogan
jglogan dismissed their stale reviewSeptember 19, 2025 00:19

Pushed the wrong button

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
return container
}

private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about code not modified in this PR:

privatenonisolatedfunc configureProcessConfig()

This should be able to become:

privatestaticfunc configureProcessConfig()

Same for closeHandle().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another lowkey obsessive nit in the non-modified code above - move getDefaultNameserver out from between the configure...() funcs. All the private methods could probably stand to be reordered sensibly. In a swift file that's pushing 1200 lines it will help new developers.

Comment threadSources/Services/ContainerSandboxService/SandboxService.swift Outdated
@Mcrich23

Mcrich23 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Hey! So sorry, but in an effort to make plugin development more possible #603 and #635 have lead to the CLI folder being renamed to ContainerCommands and that will impact the merging of your pull request. Just an FYI, so you understand the issue when you are resolving conflicts.

@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch 2 times, most recently from 560d05a to 07707d5CompareSeptember 19, 2025 10:23
@dcantah
dcantah marked this pull request as draft September 19, 2025 10:23
@dcantah

Copy link
Copy Markdown
ContributorAuthor

Converting to draft because there's a small cz change that I want in that will allow us to "fix" some behavior in stop()

Today, we have a sort of odd model all things considered. We
talk to the APIServer to do the initial container creation, which
mostly all of the work is just registering the runtime helper with
launchd. After this registration, almost all communication from a client
is talking directly to that runtime helper. This has a couple rather annoying
issues in that we now need a sort of event/notification channel that the helper
will establish with the APIServer for errors/start/exited cases.
If instead of talking to the runtime helper directly, we instead took a detour
through the APIServer, the APIServer is clued into exactly what order of operations
is occurring. This makes "did starting the container fail? Okay we should clean
up" scenarios much simpler, and it also simplifies the clients quite a bit
as they don't need this split brained client model, everyone just talks to the
APIServer. This change is in pursuit of that. I have reworked our clients, the
ContainerService and some of our XPC types to accomplish it.
The biggest "contract" changes are in the SandboxService. The first is today we have
on the flag when we register any runtime helper that makes any xpc messages wake up the
registered process. This isn't great in scenarios where the process may have crashed, or
it exited normally and we're just trying to invoke an RPC on it. Today the helper would
spawn again and try and answer our request. It'd be much nicer if we have a connection
object that will become invalid if the process that vended it to us is gone. To accomplish
this, now the runtime helpers will listen on an anonymous xpc connection and vend endpoints
from this via only one handler exposed by the SandboxService (createEndpoint). From that point
onwards all communication will be through the endpoint the service vended a client.
The second change is the runtime helper will not exit on its own when the container exits, and
the event mechanism has been removed. Now the APIServer simply calls wait() to listen for container
exit in the background, and once we get an exit we will explicitly tell the helper to shutdown.
The rationale is if shutdown is driven by the APIServer now, we can be certain we received everything
we need from the helpers before they power down.
@dcantah
dcantahforce-pushed the move-to-api-server-for-everythang branch from 07707d5 to 66fef47CompareSeptember 19, 2025 19:04
@dcantah
dcantah marked this pull request as ready for review September 19, 2025 19:26
@dcantah
dcantah requested a review from wlan0September 19, 2025 19:26
@dcantah
dcantah merged commit 444064d into apple:mainSep 20, 2025
37 of 38 checks passed

@dkovbadkovba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let h {
request.set(key: key, value: h)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code repeats 4 times. Consider extracting it into helper methods in XPCMessage:

 static func stdioKey(for index: Int) throws -> XPCKeys {
switch index {
case 0: return .stdin
case 1: return .stdout
case 2: return .stderr
default:
throw ContainerizationError(.invalidArgument, message: "invalid fd \(index)")
}
}
static func setStdioHandles(on request: XPCMessage, stdio: [FileHandle?]) throws {
for (index, handle) in stdio.enumerated() {
if let handle {
request.set(key: stdioKey(for: index), value: handle)
}
}
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John had commented the same, I'm going to do that (and some other cleanups) in a followup

@adityaramaniadityaramani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good!

jglogan added a commit that referenced this pull request Sep 23, 2025
## Motivation and Context
#654 forgot to include the shutdown XPC that was added in #628.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@dcantah@Mcrich23@jglogan@wlan0@adityaramani@dkovba