Skip to content

Conversation

weissi
Copy link
Member

@weissi weissi commented Jul 11, 2025

Motivation:

NIOThread assumed it can hold onto pthread_t forever. Unfortunately, on Linux, pthread_t is dead when the thread got joined (if not detached) or if it exits (if detached) which is at a random point in time.

Modifications:

  • Only assume ownership owner the pthread_t if not detached
  • nil out the pthread_t on join()

Result:

  • Fewer pthread_t lifecycle bugs.

@weissi weissi force-pushed the jw-niothread-refactor branch 3 times, most recently from 21622e1 to 50c018f Compare July 11, 2025 15:26
@weissi weissi changed the title Jw niothread refactor NIOThread refactor: pthread_t lifetimes Jul 11, 2025
@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Jul 11, 2025
}
"""
} else {
return self.externalStateLock.withLock {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the lock-holding concern not as relevant here?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. it already holds a lock, so I'm not making things worse I think :P
  2. it's debugDescription so maybe less likely to be called?

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically replicating prior art in the existing impl

@@ -42,41 +44,74 @@ protocol ThreadOps {
/// All methods exposed are thread-safe.
@usableFromInline
final class NIOThread: Sendable {
internal typealias ThreadBoxValue = (body: (NIOThread) -> Void, name: String?)
internal typealias ThreadBoxValue = (body: (NIOThread) -> Void, name: String?, isDetached: Bool)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we tweak this API to try to discourage creating detached threads outside of tests?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Lukasa Honestly, I'm planning a follow-up to remove the ability to use detached threads at all, we don't use them outside of tests and we don't need them in tests.

@weissi weissi mentioned this pull request Jul 14, 2025
weissi added a commit that referenced this pull request Jul 14, 2025
### Motivation:

PR #3297 is changing `inEventLoop` and `inEventLoop` is very much on the
perf path. So let's add a benchmark

### Modifications:

- Add `(!)inEventLoop` benchmark
- Fix a guaranteed deadlock in `TCPThroughputBenchmark` to make the
benchmarks runnable

### Result:

- More benchmarks
- Fewer hangs
weissi added a commit that referenced this pull request Jul 14, 2025
### Motivation:

`inEventLoop` is very much in the performance path of SwiftNIO,
especially these days with Concurrency, `NIOLoopBound` and friends.
Previously, we relied on `pthread_equal(pthread_self(), myPthread)`,
however, this could cause a number of issues.

1. Holding onto a `pthread_t` after `.join` is actually illegal (fixed
in #3297)
2. Potential ABA issues when `pthread_t` pointer are re-used for new
pthreads
3. Fix would require a lock around `myPthread` which makes things (2x
slower, even without contention)

### Modifications:

- New type `SelectableEventLoopUniqueID` which can be packed into a
`UInt`
- Attach them into a C thread local

### Result:

- Even faster than the old, incorrect version
- old: `measuring: el_in_eventloop_100M: 0.257395375, 0.241049208,
0.243188792, 0.259125916, 0.24843225, 0.229690125, 0.244281541,
0.225078834, 0.236395, 0.233305167`
- new: `measuring: el_in_eventloop_100M: 0.175561125, 0.187225625,
0.199269375, 0.19740975, 0.1922695, 0.179850958, 0.177612458,
0.17665125, 0.17897475, 0.18038775`
- More correct
- Groundwork to make #3297 not make things slower
@weissi weissi force-pushed the jw-niothread-refactor branch 2 times, most recently from 112eb96 to d22b9c5 Compare July 14, 2025 16:48
@weissi weissi requested a review from Lukasa July 14, 2025 16:48
@weissi weissi force-pushed the jw-niothread-refactor branch from d22b9c5 to 98ab4ea Compare July 14, 2025 16:49
@weissi weissi force-pushed the jw-niothread-refactor branch 4 times, most recently from f9e0e30 to 78dc04a Compare July 14, 2025 17:16
@weissi weissi force-pushed the jw-niothread-refactor branch from 78dc04a to 5c4f499 Compare July 14, 2025 17:23
@weissi weissi merged commit acca4e5 into apple:main Jul 15, 2025
41 checks passed
@weissi weissi deleted the jw-niothread-refactor branch July 15, 2025 09:03
weissi added a commit that referenced this pull request Jul 15, 2025
### Motivation:

Structured Concurrency is helpful but so far, MTELG was very difficult
to use with it mostly because Swift is still missing `async` in
`defer`s.

This builds on
- #3297 
- #3302
- #3304

### Modifications:

- Add MTELG.withELG { ... }

### Result:

MTELG + SC = <3
zaneenders pushed a commit to zaneenders/swift-nio that referenced this pull request Jul 23, 2025
### Motivation:

`inEventLoop` is very much in the performance path of SwiftNIO,
especially these days with Concurrency, `NIOLoopBound` and friends.
Previously, we relied on `pthread_equal(pthread_self(), myPthread)`,
however, this could cause a number of issues.

1. Holding onto a `pthread_t` after `.join` is actually illegal (fixed
in apple#3297)
2. Potential ABA issues when `pthread_t` pointer are re-used for new
pthreads
3. Fix would require a lock around `myPthread` which makes things (2x
slower, even without contention)

### Modifications:

- New type `SelectableEventLoopUniqueID` which can be packed into a
`UInt`
- Attach them into a C thread local

### Result:

- Even faster than the old, incorrect version
- old: `measuring: el_in_eventloop_100M: 0.257395375, 0.241049208,
0.243188792, 0.259125916, 0.24843225, 0.229690125, 0.244281541,
0.225078834, 0.236395, 0.233305167`
- new: `measuring: el_in_eventloop_100M: 0.175561125, 0.187225625,
0.199269375, 0.19740975, 0.1922695, 0.179850958, 0.177612458,
0.17665125, 0.17897475, 0.18038775`
- More correct
- Groundwork to make apple#3297 not make things slower
zaneenders pushed a commit to zaneenders/swift-nio that referenced this pull request Jul 23, 2025
### Motivation:

NIOThread assumed it can hold onto `pthread_t` forever. Unfortunately,
on Linux, `pthread_t` is dead when the thread got joined (if not
detached) or if it exits (if detached) _which is at a random point in
time_.

### Modifications:

- Only assume ownership owner the `pthread_t` if not detached
- `nil` out the `pthread_t` on `join()`

### Result:

- Fewer `pthread_t` lifecycle bugs.
zaneenders pushed a commit to zaneenders/swift-nio that referenced this pull request Jul 23, 2025
### Motivation:

Structured Concurrency is helpful but so far, MTELG was very difficult
to use with it mostly because Swift is still missing `async` in
`defer`s.

This builds on
- apple#3297 
- apple#3302
- apple#3304

### Modifications:

- Add MTELG.withELG { ... }

### Result:

MTELG + SC = <3
Lukasa pushed a commit that referenced this pull request Aug 28, 2025
…3360)

### Motivation:

In #3297, I introduced a deadlock involving `SelectableEventLoop`'s
`debugDescription`. I was under the impression that it's not possible to
call this from outside the `NIO` module so I deemed it safe. That was a
mistake :).

### Modifications:

- Make it impossible to deadlock around
`SelectableEventLoop.debugDescription`.

### Result:

- Fewer deadlocks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants