-
Notifications
You must be signed in to change notification settings - Fork 693
Description
Expected behavior
I don't get zombie channels
Actual behavior
I get a zombie channel where the writes just get forgotten that doesn't have an input or output.
Steps to reproduce
- Create a NIOPipeChannel with just an output to a pipe (support half-closure, close input)
- Have it write a load (maybe 32 MB) to that pipe (DO NOT READ IT)
- Now close that pipe on the read end
- The following things should happen now (but they don't)
a. I should get a channel error
b. My writes should fail
c. I should probably get a user event about that
SwiftNIO version/commit hash
2.83.0
System & version information
Linux
Trace
Here's a commented trace (strace
, my debug output and DebugChannelHandler
s mixed)
the registrations of fd 25
=== CHANNEL INBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = false, localAddress = nil, remoteAddress = nil }) registered
[pid 17062] epoll_ctl(19, EPOLL_CTL_ADD, 25, {events=EPOLLERR|EPOLLHUP, data={u32=25, u64=4294967321}}) = 0
=== CHANNEL INBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) active
[pid 17062] epoll_ctl(19, EPOLL_CTL_MOD, 25, {events=EPOLLERR|EPOLLHUP, data={u32=25, u64=4294967321}}) = 0
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) close(mode: NIOCore.CloseMode.input)
== SHUT RD
=== CHANNEL INBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) userInboundEventTriggered(event: NIOCore.ChannelEvent.inputClosed)
[pid 17062] epoll_pwait(19, [{events=EPOLLIN, data={u32=20, u64=18446744069414584340}}], 64, -1, NULL, 8) = 1
C WRITING [474f0a](3 bytes) to PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }
strace: Process 17067 attached
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) close(mode: NIOCore.CloseMode.input)
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) write(data: (ByteBuffer: [474f0a](3 bytes)))
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) flush
write(pointer:) 107
C FUTURE WRITE success()
so far it's all good, we're written 3 bytes into that channel. Now we're gonna write 16 MiB
[pid 17062] epoll_pwait(19, C WRITE [474f0a](3 bytes) to PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil } DONE
C WRITING [4242424242424242424242424242424242424242424242424242424242424242...4242424242424242424242424242424242424242424242424242424242424242](167772160 bytes) to PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }
[{events=EPOLLIN, data={u32=20, u64=18446744069414584340}}], 64, -1, NULL, 8) = 1
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) write(data: (ByteBuffer: [4242424242424242424242424242424242424242424242424242424242424242...4242424242424242424242424242424242424242424242424242424242424242](167772160 bytes)))
=== CHANNEL INBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) writabilityChanged(isWritable: false)
=== CHANNEL OUTBOUND (PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }) flush
write(pointer:) 107
write(pointer:) 107
cool, as expected the writabilityChanged(isWritable: false)
[pid 17062] epoll_ctl(19, EPOLL_CTL_MOD, 25, {events=EPOLLOUT|EPOLLERR|EPOLLHUP, data={u32=25, u64=4294967321}}) = 0
[... uninteresting stuff ...]
Now, we're seeing the pipe close
sleep 17070 should be dead
<unfinished ...>
[pid 17070] +++ killed by SIGKILL +++
[pid 17062] <... epoll_pwait resumed>[{events=EPOLLHUP, data={u32=28, u64=18446744069414584348}}, {events=EPOLLERR, data={u32=25, u64=4294967321}}], 64, -1, NULL, 8) = 2
See just above? events=EPOLLERR, data={u32=25
, we're getting EPOLLERR
for fd 25.
And we react (closing the pipe, the SelectableFileHandle etc) driven from writeEOF
1834
[pid 17062] epoll_ctl(19, EPOLL_CTL_MOD, 28, {events=EPOLLIN|EPOLLERR|EPOLLHUP, data={u32=28, u64=18446744069414584348}}) = 0
read(pointer:) 127
[pid 17062] epoll_ctl(19, EPOLL_CTL_DEL, 28, 0xffffb200c268) = 0
--- PIPE CLOSE Optional(SelectableFileHandle(isOpen: true, fd: 28)), nil
--- CLOSING 28 SelectableFileHandle(isOpen: false, fd: -1)
=== CALL BEGIN to writeEOF() on PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }
[pid 17062] epoll_ctl(19, EPOLL_CTL_DEL, 25, 0xffffb200cf98) = 0
--- CLOSING 25 SelectableFileHandle(isOpen: false, fd: -1)
=== CALL END to writeEOF() on PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }
and then: Nothing, no channel events, no failing futures, nothing.
And then, once a second I see (because I made a repeated task printing the channel)
C ALIVE PipeChannel { PipePair { in=nil, out=Optional(SelectableFileHandle(isOpen: true, fd: 25)) }, active = true, localAddress = nil, remoteAddress = nil }
Note the other weirdness: The PipePair
seems to cache the string, so it shows SelectableFileHandle(isOpen: true, fd: 25)
despite the fact that the SelectableFileHandle
knows it's closed