Merge nucleic/hazy-opal-yak-cjc0 into dev
This commit is contained in:
+34
-2
@@ -205,6 +205,36 @@ rebuild whenever a guest patch changes. Built locally, not in CI: the host frame
|
|||||||
into a never-accepted backlog. A failed pre-relay connection is also closed explicitly.
|
into a never-accepted backlog. A failed pre-relay connection is also closed explicitly.
|
||||||
Marked `[Nucleic vendored patch]`.
|
Marked `[Nucleic vendored patch]`.
|
||||||
|
|
||||||
|
14. **Non-blocking, non-spinning guest I/O plane (`IOPair.swift`, `OSFile+Splice.swift`,
|
||||||
|
`VsockProxy.swift`) — the root cause of the "control plane unresponsive / all sessions stall"
|
||||||
|
wedge.** Every exec's stdio relay (`IOPair`) and every control-plane relay connection
|
||||||
|
(`VsockProxy`) share ONE thread: `ProcessSupervisor.default`'s epoll poller. Three defects let
|
||||||
|
a single slow peer freeze that thread — and with it every session's stdio AND the whole
|
||||||
|
container's control plane at once (probes then read "accepts connections but never answers";
|
||||||
|
before the manager-side recovery rework the host restarted the container over this, SIGKILLing
|
||||||
|
every session):
|
||||||
|
- **`IOPair` blocking write:** only *registered* fds get `O_NONBLOCK` (set by `Epoll.add`), and
|
||||||
|
the relay registers only its READ fd — the write fd (e.g. the vsock socket carrying an exec's
|
||||||
|
stdout to the host) stayed blocking. One slow-drained stream parked the poller thread in
|
||||||
|
`write(2)`. The relay now sets the write fd non-blocking up front and implements real
|
||||||
|
backpressure: a full destination stashes the remainder in a `pending` backlog, registers the
|
||||||
|
write fd for EPOLLOUT, and suspends reads until the flush completes (throttling the producing
|
||||||
|
process via its own pipe, not the shared thread). The old close-on-short-write path — dead
|
||||||
|
while the fd was blocking, live and stdio-dropping once non-blocking — is subsumed by the
|
||||||
|
backlog; genuine write errors still close the pair.
|
||||||
|
- **`OSFile.splice` busy-spin:** when the destination was full (EAGAIN) and the source idle,
|
||||||
|
the outer loop had no exit — it spun the poller thread at 100% until the peer drained (or
|
||||||
|
forever if it never did). The flush leg's EAGAIN branch now returns partial progress; the
|
||||||
|
un-flushed bytes stay in the transfer pipe and the destination's EPOLLOUT edge resumes the
|
||||||
|
flush (both `VsockProxy` handlers already pump both directions on both events).
|
||||||
|
- **`VsockProxy` registration race:** the client fd's epoll handler can fire — and splice
|
||||||
|
toward the server fd — before the second registration makes that fd non-blocking; a full
|
||||||
|
destination made that a genuinely blocking splice on the poller thread. Both fds are now set
|
||||||
|
non-blocking before either is registered.
|
||||||
|
All three are guest-side and INERT until the initfs image is rebuilt (`make vminit-image`, tag
|
||||||
|
`0.34.0-nucleic4`) and `ContainerEngine.vminitReference` is bumped after runtime validation.
|
||||||
|
Marked `[Nucleic vendored patch]`.
|
||||||
|
|
||||||
## Re-vendoring a newer upstream commit
|
## Re-vendoring a newer upstream commit
|
||||||
|
|
||||||
1. `git clone` upstream (or copy `.build/checkouts/containerization` after bumping the URL pin
|
1. `git clone` upstream (or copy `.build/checkouts/containerization` after bumping the URL pin
|
||||||
@@ -222,9 +252,11 @@ rebuild whenever a guest patch changes. Built locally, not in CI: the host frame
|
|||||||
`Cgroup2Manager.swift` / `ManagedContainer.swift` / `ManagedProcess.swift`, patch #10
|
`Cgroup2Manager.swift` / `ManagedContainer.swift` / `ManagedProcess.swift`, patch #10
|
||||||
(`Socket.acceptStream` transient-error tolerance + `isTransientAcceptError`), patch #11 (the
|
(`Socket.acceptStream` transient-error tolerance + `isTransientAcceptError`), patch #11 (the
|
||||||
`UnixSocketRelay` per-connection containment + fail-fast closes), patch #12 (the `VsockProxy`
|
`UnixSocketRelay` per-connection containment + fail-fast closes), patch #12 (the `VsockProxy`
|
||||||
cleanup/`try!`/listener hardening in `vminitd/`), and patch #13 (the prompt-free
|
cleanup/`try!`/listener hardening in `vminitd/`), patch #13 (the prompt-free
|
||||||
`KeychainQuery` reads: `withoutInteractiveUI` + the `errSecInteractionNotAllowed` handling +
|
`KeychainQuery` reads: `withoutInteractiveUI` + the `errSecInteractionNotAllowed` handling +
|
||||||
the `save` duplicate retry). After re-applying any
|
the `save` duplicate retry), and patch #14 (the non-blocking/non-spinning guest I/O plane:
|
||||||
|
`IOPair` backpressure, the `OSFile.splice` EAGAIN return, and the `VsockProxy` pre-registration
|
||||||
|
non-blocking fds — all in `vminitd/`). After re-applying any
|
||||||
`vminitd/` patch, rebuild + publish the custom init image
|
`vminitd/` patch, rebuild + publish the custom init image
|
||||||
with `make vminit-image` + `make vminit-image-push`, and bump `ContainerEngine.vminitReference`.
|
with `make vminit-image` + `make vminit-image-push`, and bump `ContainerEngine.vminitReference`.
|
||||||
5. Update the commit hash above and in the root `Package.swift` comment.
|
5. Update the commit hash above and in the root `Package.swift` comment.
|
||||||
|
|||||||
+174
-165
@@ -1,249 +1,258 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "6ccceb47b6a402e9ac07d23204ec7f4792823b22b96275cd67f8531787a60c04",
|
"originHash": "264b211a5ea74fa24ced86faade5901700722c925484a26379cc4a6b40083c6c",
|
||||||
"pins" : [
|
"pins": [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity": "async-http-client",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/swift-server/async-http-client.git",
|
"location": "https://github.com/swift-server/async-http-client.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "4b99975677236d13f0754339864e5360142ff5a1",
|
"revision": "4603a8036d921ea999fadb742931546c341f4bd7",
|
||||||
"version" : "1.30.3"
|
"version": "1.35.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "grpc-swift-2",
|
"identity": "grpc-swift-2",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/grpc/grpc-swift-2.git",
|
"location": "https://github.com/grpc/grpc-swift-2.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "f28854bc760a116e053fdfc4a48a9428c34625c0",
|
"revision": "28cdd63ef88583ddc67d7bb179eab46fab465ce9",
|
||||||
"version" : "2.3.0"
|
"version": "2.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "grpc-swift-nio-transport",
|
"identity": "grpc-swift-nio-transport",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/grpc/grpc-swift-nio-transport.git",
|
"location": "https://github.com/grpc/grpc-swift-nio-transport.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "f37e0c2d293cea668b11e10e1fb1c24cb40781ff",
|
"revision": "2ca31f06658ed288a2560e23ad649acbb3d6b3a3",
|
||||||
"version" : "2.4.4"
|
"version": "2.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "grpc-swift-protobuf",
|
"identity": "grpc-swift-protobuf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/grpc/grpc-swift-protobuf.git",
|
"location": "https://github.com/grpc/grpc-swift-protobuf.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "19153231a03c2fda1f4ea60da1b92a2cb9c011d8",
|
"revision": "176c5a434fd76f6f479848d1a8f7d44967534168",
|
||||||
"version" : "2.2.0"
|
"version": "2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-algorithms",
|
"identity": "swift-algorithms",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-algorithms.git",
|
"location": "https://github.com/apple/swift-algorithms.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
|
"revision": "87e50f483c54e6efd60e885f7f5aa946cee68023",
|
||||||
"version" : "1.2.1"
|
"version": "1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-argument-parser",
|
"identity": "swift-argument-parser",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
"location": "https://github.com/apple/swift-argument-parser.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615",
|
"revision": "6a52f3251125d74daf04fcbd5e6f08a75d074382",
|
||||||
"version" : "1.7.0"
|
"version": "1.8.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-asn1",
|
"identity": "swift-asn1",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-asn1.git",
|
"location": "https://github.com/apple/swift-asn1.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "a54383ada6cecde007d374f58f864e29370ba5c3",
|
"revision": "a9a5efd40eaf558a2bcd48d64b1d1646be686008",
|
||||||
"version" : "1.3.2"
|
"version": "1.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-async-algorithms",
|
"identity": "swift-async-algorithms",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-async-algorithms.git",
|
"location": "https://github.com/apple/swift-async-algorithms.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b",
|
"revision": "3da39bbc4e687d4192af7c9cf4eab805745a0b9c",
|
||||||
"version" : "1.0.4"
|
"version": "1.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-atomics",
|
"identity": "swift-atomics",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-atomics.git",
|
"location": "https://github.com/apple/swift-atomics.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
|
"revision": "0442cb5a3f98ab802acb777929fdb446bda11a34",
|
||||||
"version" : "1.2.0"
|
"version": "1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-certificates",
|
"identity": "swift-certificates",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-certificates.git",
|
"location": "https://github.com/apple/swift-certificates.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "f4cd9e78a1ec209b27e426a5f5c693675f95e75a",
|
"revision": "89fbc3714264cce8db8e4ec51b64e01c3e28c6c5",
|
||||||
"version" : "1.15.0"
|
"version": "1.19.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-collections",
|
"identity": "swift-collections",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-collections.git",
|
"location": "https://github.com/apple/swift-collections.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0",
|
"revision": "a0cb0954ecb21e4e31b0070e6ed5674e8556685a",
|
||||||
"version" : "1.2.0"
|
"version": "1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-crypto",
|
"identity": "swift-configuration",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-crypto.git",
|
"location": "https://github.com/apple/swift-configuration.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed",
|
"revision": "be76c4ad929eb6c4bcaf3351799f2adf9e6848a9",
|
||||||
"version" : "3.12.3"
|
"version": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-distributed-tracing",
|
"identity": "swift-crypto",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-distributed-tracing.git",
|
"location": "https://github.com/apple/swift-crypto.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "dc4030184203ffafbb2ec614352487235d747fe0",
|
"revision": "95ba0316a9b733e92bb6b071255ff46263bbe7dc",
|
||||||
"version" : "1.4.1"
|
"version": "3.15.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-http-structured-headers",
|
"identity": "swift-distributed-tracing",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-http-structured-headers.git",
|
"location": "https://github.com/apple/swift-distributed-tracing.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "db6eea3692638a65e2124990155cd220c2915903",
|
"revision": "dc4030184203ffafbb2ec614352487235d747fe0",
|
||||||
"version" : "1.3.0"
|
"version": "1.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-http-types",
|
"identity": "swift-http-structured-headers",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-http-types.git",
|
"location": "https://github.com/apple/swift-http-structured-headers.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "a0a57e949a8903563aba4615869310c0ebf14c03",
|
"revision": "933538faa42c432d385f02e07df0ace7c5ecfc47",
|
||||||
"version" : "1.4.0"
|
"version": "1.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-log",
|
"identity": "swift-http-types",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-log.git",
|
"location": "https://github.com/apple/swift-http-types.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523",
|
"revision": "db774a277f60063a32d854f2980299caf06da041",
|
||||||
"version" : "1.10.1"
|
"version": "1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-nio",
|
"identity": "swift-log",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-nio.git",
|
"location": "https://github.com/apple/swift-log.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "4e8f4b1c9adaa59315c523540c1ff2b38adc20a9",
|
"revision": "a878e7f8f46cfc0e1125e565b5c08e7d5272dc9a",
|
||||||
"version" : "2.87.0"
|
"version": "1.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-nio-extras",
|
"identity": "swift-nio",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
"location": "https://github.com/apple/swift-nio.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "145db1962f4f33a4ea07a32e751d5217602eea29",
|
"revision": "cd3e1152083706d77b223fb29110e590efcc70c0",
|
||||||
"version" : "1.28.0"
|
"version": "2.101.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-nio-http2",
|
"identity": "swift-nio-extras",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
"location": "https://github.com/apple/swift-nio-extras.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5",
|
"revision": "88a51340f59cf181ebde888bd1b749296b3ec029",
|
||||||
"version" : "1.38.0"
|
"version": "1.34.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-nio-ssl",
|
"identity": "swift-nio-http2",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-nio-ssl.git",
|
"location": "https://github.com/apple/swift-nio-http2.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "173cc69a058623525a58ae6710e2f5727c663793",
|
"revision": "61d1b44f6e4e118792be1cff88ee2bc0267c6f9a",
|
||||||
"version" : "2.36.0"
|
"version": "1.44.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-nio-transport-services",
|
"identity": "swift-nio-ssl",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-nio-transport-services.git",
|
"location": "https://github.com/apple/swift-nio-ssl.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56",
|
"revision": "407d82d5b6cc00e1c3fb83a81b1539b70c788c5e",
|
||||||
"version" : "1.24.0"
|
"version": "2.37.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-numerics",
|
"identity": "swift-nio-transport-services",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-numerics.git",
|
"location": "https://github.com/apple/swift-nio-transport-services.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8",
|
"revision": "67787bb645a5e67d2edcdfbe48a216cc549222d5",
|
||||||
"version" : "1.0.3"
|
"version": "1.28.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-protobuf",
|
"identity": "swift-numerics",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
"location": "https://github.com/apple/swift-numerics.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "86970144a0b86068c81ff48ee29b3f97cae0b879",
|
"revision": "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
|
||||||
"version" : "1.36.0"
|
"version": "1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-service-context",
|
"identity": "swift-protobuf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-service-context.git",
|
"location": "https://github.com/apple/swift-protobuf.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "d0997351b0c7779017f88e7a93bc30a1878d7f29",
|
"revision": "55d7a1cc5666b85c13464aea1c4b4a90feccb4c8",
|
||||||
"version" : "1.3.0"
|
"version": "1.38.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-service-lifecycle",
|
"identity": "swift-service-context",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/swift-server/swift-service-lifecycle.git",
|
"location": "https://github.com/apple/swift-service-context.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "e7187309187695115033536e8fc9b2eb87fd956d",
|
"revision": "d0997351b0c7779017f88e7a93bc30a1878d7f29",
|
||||||
"version" : "2.8.0"
|
"version": "1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-system",
|
"identity": "swift-service-lifecycle",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-system.git",
|
"location": "https://github.com/swift-server/swift-service-lifecycle.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
|
"revision": "9829955b385e5bb88128b73f1b8389e9b9c3191a",
|
||||||
"version" : "1.6.4"
|
"version": "2.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "zstd",
|
"identity": "swift-system",
|
||||||
"kind" : "remoteSourceControl",
|
"kind": "remoteSourceControl",
|
||||||
"location" : "https://github.com/facebook/zstd.git",
|
"location": "https://github.com/apple/swift-system.git",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "f8745da6ff1ad1e7bab384bd1f9d742439278e99",
|
"revision": "b5544ba79a70a0cb3563e75bf26dc198d6b40ed3",
|
||||||
"version" : "1.5.7"
|
"version": "1.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity": "zstd",
|
||||||
|
"kind": "remoteSourceControl",
|
||||||
|
"location": "https://github.com/facebook/zstd.git",
|
||||||
|
"state": {
|
||||||
|
"revision": "f8745da6ff1ad1e7bab384bd1f9d742439278e99",
|
||||||
|
"version": "1.5.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version": 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ final class IOPair: Sendable {
|
|||||||
let buffer: UnsafeMutableBufferPointer<UInt8>
|
let buffer: UnsafeMutableBufferPointer<UInt8>
|
||||||
var closed: Bool
|
var closed: Bool
|
||||||
var registeredFd: Int32?
|
var registeredFd: Int32?
|
||||||
|
// [Nucleic vendored patch] Backpressure state: bytes read from `from` that `to` couldn't
|
||||||
|
// take yet (its buffer was full), plus whether `to` is currently registered for EPOLLOUT
|
||||||
|
// to flush them. While `pending` is non-empty the relay reads nothing more — the source
|
||||||
|
// pipe backs up and throttles the *producing process* instead of this thread. The write
|
||||||
|
// fd used to be BLOCKING (only registered fds get O_NONBLOCK, and it never was), so one
|
||||||
|
// slow-drained stream parked the shared ProcessSupervisor poller thread — freezing every
|
||||||
|
// exec's stdio and every control-plane relay in the container at once.
|
||||||
|
var pending: [UInt8]
|
||||||
|
var pendingOffset: Int
|
||||||
|
var writeFdRegistered: Bool
|
||||||
|
|
||||||
func drain() {
|
func drain() {
|
||||||
let readFrom = OSFile(fd: from.fileDescriptor)
|
let readFrom = OSFile(fd: from.fileDescriptor)
|
||||||
@@ -66,10 +76,27 @@ final class IOPair: Sendable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Nucleic vendored patch] Flush what we can IN ORDER: the pending backlog first,
|
||||||
|
// then (only if it fully flushed) a best-effort drain of the source. Draining with
|
||||||
|
// unsent pending bytes would reorder the stream.
|
||||||
|
let writeTo = OSFile(fd: to.fileDescriptor)
|
||||||
|
while pendingOffset < pending.count {
|
||||||
|
let offset = pendingOffset
|
||||||
|
let result = pending.withUnsafeMutableBufferPointer { buf in
|
||||||
|
writeTo.write(
|
||||||
|
UnsafeMutableBufferPointer(
|
||||||
|
start: buf.baseAddress!.advanced(by: offset),
|
||||||
|
count: buf.count - offset))
|
||||||
|
}
|
||||||
|
if result.wrote > 0 { pendingOffset += result.wrote }
|
||||||
|
if result.action != .success { break }
|
||||||
|
}
|
||||||
|
if pendingOffset >= pending.count {
|
||||||
// Try and drain IO first.
|
// Try and drain IO first.
|
||||||
self.drain()
|
self.drain()
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the fd from our global epoll instance first.
|
// Remove the fds from our global epoll instance first.
|
||||||
if let fd = self.registeredFd {
|
if let fd = self.registeredFd {
|
||||||
do {
|
do {
|
||||||
try ProcessSupervisor.default.unregisterFd(fd)
|
try ProcessSupervisor.default.unregisterFd(fd)
|
||||||
@@ -78,6 +105,15 @@ final class IOPair: Sendable {
|
|||||||
}
|
}
|
||||||
self.registeredFd = nil
|
self.registeredFd = nil
|
||||||
}
|
}
|
||||||
|
// [Nucleic vendored patch] The write fd may be registered for backpressure flushing.
|
||||||
|
if self.writeFdRegistered {
|
||||||
|
do {
|
||||||
|
try ProcessSupervisor.default.unregisterFd(to.fileDescriptor)
|
||||||
|
} catch {
|
||||||
|
logger?.error("failed to delete write fd from epoll \(to.fileDescriptor): \(error)")
|
||||||
|
}
|
||||||
|
self.writeFdRegistered = false
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try self.from.close()
|
try self.from.close()
|
||||||
@@ -108,7 +144,10 @@ final class IOPair: Sendable {
|
|||||||
to: writeTo,
|
to: writeTo,
|
||||||
buffer: buffer,
|
buffer: buffer,
|
||||||
closed: false,
|
closed: false,
|
||||||
registeredFd: nil
|
registeredFd: nil,
|
||||||
|
pending: [],
|
||||||
|
pendingOffset: 0,
|
||||||
|
writeFdRegistered: false
|
||||||
))
|
))
|
||||||
self.reason = reason
|
self.reason = reason
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
@@ -122,8 +161,16 @@ final class IOPair: Sendable {
|
|||||||
return (io.from.fileDescriptor, io.to.fileDescriptor)
|
return (io.from.fileDescriptor, io.to.fileDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let readFrom = OSFile(fd: readFromFd)
|
// [Nucleic vendored patch] The write fd must be non-blocking BEFORE the first relay write.
|
||||||
let writeTo = OSFile(fd: writeToFd)
|
// `Epoll.add` only sets O_NONBLOCK on fds it registers, and the write fd is registered only
|
||||||
|
// on demand (EPOLLOUT backpressure below) — so without this, the very first full-buffer
|
||||||
|
// write blocked the shared poller thread.
|
||||||
|
let flags = fcntl(writeToFd, F_GETFL)
|
||||||
|
if flags == -1 || fcntl(writeToFd, F_SETFL, flags | O_NONBLOCK) == -1 {
|
||||||
|
self.logger?.error(
|
||||||
|
"failed to set relay write fd non-blocking",
|
||||||
|
metadata: ["fd": "\(writeToFd)", "errno": "\(errno)"])
|
||||||
|
}
|
||||||
|
|
||||||
try ProcessSupervisor.default.registerFd(readFromFd, mask: .input) { mask in
|
try ProcessSupervisor.default.registerFd(readFromFd, mask: .input) { mask in
|
||||||
self.io.withLock { io in
|
self.io.withLock { io in
|
||||||
@@ -139,7 +186,49 @@ final class IOPair: Sendable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop so we drain fully.
|
self.pump(&io, mask: mask, ignoreHup: ignoreHup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Nucleic vendored patch] One relay pass, non-blocking end to end: flush any pending
|
||||||
|
/// backlog toward `to`, then (only once it's empty) drain `from`. On a full destination the
|
||||||
|
/// remainder is stashed in `pending` and the write fd registered for EPOLLOUT, whose edge
|
||||||
|
/// re-enters this pump — so backpressure suspends the relay instead of blocking or spinning
|
||||||
|
/// the shared poller thread. Must be called with the `io` lock held.
|
||||||
|
private func pump(_ io: inout IO, mask: Epoll.Mask, ignoreHup: Bool) {
|
||||||
|
let readFrom = OSFile(fd: io.from.fileDescriptor)
|
||||||
|
let writeTo = OSFile(fd: io.to.fileDescriptor)
|
||||||
|
|
||||||
|
// Flush the pending backlog first; reads stay suspended until it clears.
|
||||||
|
while io.pendingOffset < io.pending.count {
|
||||||
|
let offset = io.pendingOffset
|
||||||
|
let result = io.pending.withUnsafeMutableBufferPointer { buf in
|
||||||
|
writeTo.write(
|
||||||
|
UnsafeMutableBufferPointer(
|
||||||
|
start: buf.baseAddress!.advanced(by: offset),
|
||||||
|
count: buf.count - offset))
|
||||||
|
}
|
||||||
|
if result.wrote > 0 { io.pendingOffset += result.wrote }
|
||||||
|
switch result.action {
|
||||||
|
case .success:
|
||||||
|
continue
|
||||||
|
case .again:
|
||||||
|
self.ensureWriteRegistered(&io)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
self.logger?.error("stopping relay: write failed during backlog flush")
|
||||||
|
io.close(logger: self.logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !io.pending.isEmpty {
|
||||||
|
io.pending = []
|
||||||
|
io.pendingOffset = 0
|
||||||
|
self.unregisterWrite(&io)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop so we drain fully (edge-triggered epoll requires reading until EAGAIN).
|
||||||
while true {
|
while true {
|
||||||
let r = readFrom.read(io.buffer)
|
let r = readFrom.read(io.buffer)
|
||||||
if r.read > 0 {
|
if r.read > 0 {
|
||||||
@@ -150,24 +239,38 @@ final class IOPair: Sendable {
|
|||||||
|
|
||||||
let w = writeTo.write(view)
|
let w = writeTo.write(view)
|
||||||
if w.wrote != r.read {
|
if w.wrote != r.read {
|
||||||
|
switch w.action {
|
||||||
|
case .again:
|
||||||
|
// Destination full: stash the remainder and suspend reads until its
|
||||||
|
// EPOLLOUT edge flushes it. (A later `read` re-reports EOF if this
|
||||||
|
// chunk was the stream's last, so no EOF is lost by returning here.)
|
||||||
|
io.pending = Array(
|
||||||
|
UnsafeBufferPointer(
|
||||||
|
start: io.buffer.baseAddress!.advanced(by: w.wrote),
|
||||||
|
count: r.read - w.wrote))
|
||||||
|
io.pendingOffset = 0
|
||||||
|
self.ensureWriteRegistered(&io)
|
||||||
|
return
|
||||||
|
default:
|
||||||
self.logger?.error("stopping relay: short write for stdio")
|
self.logger?.error("stopping relay: short write for stdio")
|
||||||
io.close(logger: self.logger)
|
io.close(logger: self.logger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch r.action {
|
switch r.action {
|
||||||
case .error(let errno):
|
case .error(let errno):
|
||||||
self.logger?.error("failed with errno \(errno) while reading for fd \(readFromFd)")
|
self.logger?.error("failed with errno \(errno) while reading for fd \(io.from.fileDescriptor)")
|
||||||
fallthrough
|
fallthrough
|
||||||
case .eof:
|
case .eof:
|
||||||
self.logger?.debug("closing relay for \(readFromFd)")
|
self.logger?.debug("closing relay for \(io.from.fileDescriptor)")
|
||||||
io.close(logger: self.logger)
|
io.close(logger: self.logger)
|
||||||
return
|
return
|
||||||
case .again:
|
case .again:
|
||||||
if mask.isHangup && !ignoreHup {
|
if mask.isHangup && !ignoreHup {
|
||||||
self.logger?.error("received EPOLLHUP and EAGAIN exiting")
|
self.logger?.error("received EPOLLHUP and EAGAIN exiting")
|
||||||
self.close()
|
io.close(logger: self.logger)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
@@ -175,6 +278,37 @@ final class IOPair: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Nucleic vendored patch] Register the write fd for EPOLLOUT so the pending backlog is
|
||||||
|
/// flushed when the destination drains. Registration failure closes the pair — without the
|
||||||
|
/// flush wakeup the relay would hang with data stranded. Must be called with the lock held.
|
||||||
|
private func ensureWriteRegistered(_ io: inout IO) {
|
||||||
|
guard !io.writeFdRegistered else { return }
|
||||||
|
let writeToFd = io.to.fileDescriptor
|
||||||
|
do {
|
||||||
|
try ProcessSupervisor.default.registerFd(writeToFd, mask: .output) { _ in
|
||||||
|
self.io.withLock { io in
|
||||||
|
guard !io.closed else { return }
|
||||||
|
// An empty mask: HUP/EOF handling rides the read fd's own events.
|
||||||
|
self.pump(&io, mask: [], ignoreHup: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io.writeFdRegistered = true
|
||||||
|
} catch {
|
||||||
|
self.logger?.error("failed to register relay write fd for backpressure: \(error)")
|
||||||
|
io.close(logger: self.logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Nucleic vendored patch] Drop the EPOLLOUT registration once the backlog has flushed.
|
||||||
|
/// Must be called with the lock held.
|
||||||
|
private func unregisterWrite(_ io: inout IO) {
|
||||||
|
guard io.writeFdRegistered else { return }
|
||||||
|
io.writeFdRegistered = false
|
||||||
|
do {
|
||||||
|
try ProcessSupervisor.default.unregisterFd(io.to.fileDescriptor)
|
||||||
|
} catch {
|
||||||
|
self.logger?.error("failed to unregister relay write fd: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,17 @@ extension OSFile {
|
|||||||
if errno != EAGAIN && errno != EIO {
|
if errno != EAGAIN && errno != EIO {
|
||||||
throw POSIXError(.init(rawValue: errno)!)
|
throw POSIXError(.init(rawValue: errno)!)
|
||||||
}
|
}
|
||||||
break
|
// [Nucleic vendored patch] Destination full: RETURN, don't `break`. Breaking
|
||||||
|
// sent the outer `while true` straight back here — with the source idle and the
|
||||||
|
// destination still full, neither leg could progress and this spun the caller's
|
||||||
|
// thread at 100% until the peer drained. The caller is an epoll handler on
|
||||||
|
// vminitd's SINGLE ProcessSupervisor poller thread, so the spin froze every
|
||||||
|
// exec's stdio and every control-plane relay in the container at once (the
|
||||||
|
// all-sessions "produced no output within 60s" stall / dead control plane).
|
||||||
|
// The un-flushed bytes stay in the transfer pipe (`from.offset > to.offset`
|
||||||
|
// persists in the SpliceFiles); the destination fd is registered for EPOLLOUT,
|
||||||
|
// whose edge re-enters transferData and resumes the flush.
|
||||||
|
return (from.offset - fromOffset, to.offset - toOffset, .success)
|
||||||
}
|
}
|
||||||
to.offset += bytesWrote
|
to.offset += bytesWrote
|
||||||
if bytesWrote == 0 {
|
if bytesWrote == 0 {
|
||||||
|
|||||||
@@ -244,6 +244,22 @@ extension VsockProxy {
|
|||||||
|
|
||||||
try relayTo.connect()
|
try relayTo.connect()
|
||||||
|
|
||||||
|
// [Nucleic vendored patch] BOTH fds must be non-blocking BEFORE either is
|
||||||
|
// registered. `Epoll.add` sets O_NONBLOCK only at registration time, and the first
|
||||||
|
// (client) registration's handler can fire — and splice toward the server fd —
|
||||||
|
// before the second (server) registration has made that fd non-blocking. A full
|
||||||
|
// destination then turned the splice into a genuinely BLOCKING call on vminitd's
|
||||||
|
// single ProcessSupervisor poller thread, freezing every exec's stdio and every
|
||||||
|
// control-plane relay in the container until the peer drained.
|
||||||
|
for fd in [conn.fileDescriptor, relayTo.fileDescriptor] {
|
||||||
|
let flags = fcntl(fd, F_GETFL)
|
||||||
|
if flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 {
|
||||||
|
self.log?.error(
|
||||||
|
"failed to set proxy fd non-blocking",
|
||||||
|
metadata: ["fd": "\(fd)", "errno": "\(errno)"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `clientFile` isn't used concurrently.
|
// `clientFile` isn't used concurrently.
|
||||||
nonisolated(unsafe) var clientFile = OSFile.SpliceFile(fd: conn.fileDescriptor)
|
nonisolated(unsafe) var clientFile = OSFile.SpliceFile(fd: conn.fileDescriptor)
|
||||||
nonisolated(unsafe) var eofFromClient = false
|
nonisolated(unsafe) var eofFromClient = false
|
||||||
|
|||||||
Reference in New Issue
Block a user