Skip to content

Conversation

@orobio
Copy link
Contributor

@orobio orobio commented Jan 9, 2025

This fixes hitting the following preconditionFailure in NIOAsyncWriter: "This should have already been handled by yield()".

It doesn't expect a yield to be suspended when the state is .writerFinished, but this can definitely happen. This seemed to be the correct solution to me, but please check it carefully, because I'm unfamiliar with this code.

I'm not happy about the test for this. It's blocking the didYield call for a while, to make sure the correct state is reached. See also the 'FIXME' line. What would be a better way to do this?

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

This is a great catch, thanks so much!

Regarding your test, it might be useful for the test to use a new delegate that makes it easier to arrange the ordering to work out. NIO's ConditionLock can be used to set up a nice ordering chain, so that instead of using the usleeps we can just block on that lock being unlocked with the right value.

@orobio orobio force-pushed the NIOAsyncWriter-Fix-suspending-yield-when-writer-finished branch from bf18517 to f15a87b Compare January 14, 2025 08:05
@orobio
Copy link
Contributor Author

orobio commented Jan 14, 2025

@Lukasa : I updated it with ConditionLock. Is this what you had in mind?

This is still blocking a thread from the concurrency pool and then depending on another thread to unblock it. Will that not be a problem? The only way I see around that is to add an async hook to NIOAsyncWriter.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 14, 2025

For testing purposes, that blocking will be safe. We will eventually resolve it, and we don't rely on Dispatch to make that happen (it requires the NIO thread to do it), so it'll be ok.

@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Jan 14, 2025
Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

This is a really lovely patch, thanks so much @orobio!

@Lukasa Lukasa enabled auto-merge (squash) January 14, 2025 15:12
@Lukasa Lukasa merged commit 6fb31ea into apple:main Jan 14, 2025
34 of 35 checks passed
@orobio
Copy link
Contributor Author

orobio commented Jan 14, 2025

For testing purposes, that blocking will be safe. We will eventually resolve it, and we don't rely on Dispatch to make that happen (it requires the NIO thread to do it), so it'll be ok.

I'm not sure I fully understand what you're saying here, but I assume it's fine then.

I do still have that 'FIXME' line in there, which would probably be good to remove. Well..., this got merged while I was typing, so I'll open a new pull request for that.

Edit: Opened #3059

orobio added a commit to orobio/swift-nio that referenced this pull request Jan 14, 2025
This was left in when pull request apple#3044 was merged, but as discussed on
that pull request, this doesn't need to be fixed.
Lukasa pushed a commit that referenced this pull request Jan 14, 2025
This was left in when pull request #3044 was merged, but as discussed on
that pull request, this doesn't need to be fixed.
@finagolfin
Copy link
Contributor

It appears this pull gets the NIO tests to hang in the Android x86_64 emulator, but weirdly only when compiled and run on macOS, ie the linux CI runs shown there work fine. I don't know if that's a macOS cross-compilation issue or some incompatibility with the macOS emulator triggered by this pull, possibly both. Reverting this pull gets my Android CI runs on macOS to work again.

Let me know what you guys think.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 16, 2025

That’s very interesting. Do you have a smaller repro? The OS-specificity makes me think this is probably a Swift bug, but if it’s easy enough to reproduce we may be able to figure out if the test that hangs is just flaky.

@finagolfin
Copy link
Contributor

A bit tough for me to reproduce since I don't use macOS. @marcprux, any interest in looking into this on your Mac?

@marcprux
Copy link
Contributor

marcprux commented Jan 16, 2025

When I test against a local Android emulator (with skip android test) on my ARM macOS 15 machine with your released 6.0.3 Android SDK, the NIOAsyncWriterTests tests (which appears to be where your CI is hanging) do all pass:

Test Suite 'NIOAsyncWriterTests' passed at 2025-01-16 09:23:46.416
	 Executed 33 tests, with 0 failures (0 unexpected) in 0.022 (0.022) seconds

However, the test run does ultimately fail due to a leaked promise.

Edit: see next comment.

I also set up an Android CI in a fork of swift-nio and both macos-13 and ubuntu-24.04. In the run, macOS hangs like you are seeing and Ubuntu fails with the same leaked promise fatalError. I tried both with and without applying your patches in the workflow.

I'm not sure why the CI's macOS testing hangs but my local tests complete, but one difference is that the CI's Android emulator is API 29 and mine is API 34. I'll try with some different Android API levels and see if that changes anything.

Another possibility is architecture: my local emulator is running on aarch64, but the CI runs x86_64 (which is why we are running on macos-13: it is the last public macOS image to support x86). I won't have a macOS x86 machine handy, but I can get access to one later on, so I can try that out if you suspect that it could be architecture related.

Here's the leaked promise, although it is likely a red herring:

Test Case 'ChannelTests.testAskForLocalAndRemoteAddressesAfterChannelIsClosed' started at 2025-01-16 09:23:53.419
NIOPosixTests/TestUtils.swift:697: Fatal error: leaking promise created at (file: "NIOPosixTests/TestUtils.swift", line: 697)
Current stack trace:
0    libswiftCore.so                    0x00000075619ad878 _swift_stdlib_reportFatalErrorInFile + 128
1    libswiftCore.so                    0x00000075617cfb64 _assertionFailure(_:_:file:line:flags:) + 244
2    swift-nioPackageTests.xctest       0x00000061dced52f0 <unavailable> + 19673840
3    swift-nioPackageTests.xctest       0x00000061dcf77a40 <unavailable> + 20339264
4    swift-nioPackageTests.xctest       0x00000061dcf77e34 <unavailable> + 20340276
5    swift-nioPackageTests.xctest       0x00000061dcee49ec <unavailable> + 19737068
6    swift-nioPackageTests.xctest       0x00000061dcee4aac <unavailable> + 19737260
7    libswiftCore.so                    0x000000756191a79c <unavailable> + 4884380
8    libswiftCore.so                    0x000000756191b274 <unavailable> + 4887156
9    swift-nioPackageTests.xctest       0x00000061dda7ca48 <unavailable> + 31894088
10   swift-nioPackageTests.xctest       0x00000061dda705b0 <unavailable> + 31843760
11   swift-nioPackageTests.xctest       0x00000061dda7334c <unavailable> + 31855436
12   swift-nioPackageTests.xctest       0x00000061dda754fc <unavailable> + 31864060
13   swift-nioPackageTests.xctest       0x00000061dda7cf88 <unavailable> + 31895432
14   swift-nioPackageTests.xctest       0x00000061dda7bab4 <unavailable> + 31890100
15   swift-nioPackageTests.xctest       0x00000061dda7b4c8 <unavailable> + 31888584
16   swift-nioPackageTests.xctest       0x00000061dd7611d4 <unavailable> + 28635604
17   swift-nioPackageTests.xctest       0x00000061dd76e1b8 <unavailable> + 28688824
18   libXCTest.so                       0x000000755b6ccbcc <unavailable> + 236492
19   libXCTest.so                       0x000000755b6ce564 XCTAssertNoThrow<A>(_:_:file:line:) + 68
20   swift-nioPackageTests.xctest       0x00000061dd7907cc <unavailable> + 28829644
21   swift-nioPackageTests.xctest       0x00000061ddd54300 <unavailable> + 34874112
22   swift-nioPackageTests.xctest       0x00000061dce81c6c <unavailable> + 19332204
23   swift-nioPackageTests.xctest       0x00000061ddd829ac <unavailable> + 35064236
24   libXCTest.so                       0x000000755b6c5c44 <unavailable> + 207940
25   libXCTest.so                       0x000000755b6c50bc <unavailable> + 204988
26   libXCTest.so                       0x000000755b6c5bac <unavailable> + 207788
27   libXCTest.so                       0x000000755b6c5b8c <unavailable> + 207756
28   libXCTest.so                       0x000000755b6c5f00 <unavailable> + 208640
29   libXCTest.so                       0x000000755b6c405c <unavailable> + 200796
30   libXCTest.so                       0x000000755b6c6164 XCTestCase.invokeTest() + 356
31   libXCTest.so                       0x000000755b6c5f48 XCTestCase.perform(_:) + 128
32   libXCTest.so                       0x000000755b6c8a54 XCTest.run() + 164
33   libXCTest.so                       0x000000755b6c706c XCTestSuite.perform(_:) + 164
34   libXCTest.so                       0x000000755b6c8a54 XCTest.run() + 164
35   libXCTest.so                       0x000000755b6c706c XCTestSuite.perform(_:) + 164
36   libXCTest.so                       0x000000755b6c8a54 XCTest.run() + 164
37   libXCTest.so                       0x000000755b6c706c XCTestSuite.perform(_:) + 164
38   libXCTest.so                       0x000000755b6c8a54 XCTest.run() + 164
39   libXCTest.so                       0x000000755b6c40ec XCTMain(_:arguments:observers:) + 312
40   libXCTest.so                       0x000000755b6c42c4 XCTMain(_:) + 40
41   swift-nioPackageTests.xctest       0x00000061ddddf170 <unavailable> + 35443056
42   swift-nioPackageTests.xctest       0x00000061ddddf190 <unavailable> + 35443088
43   swift-nioPackageTests.xctest       0x00000061ddddf1a8 <unavailable> + 35443112
44   libc.so                            0x000000755d8f25a8 __libc_init + 108
Trap 

@marcprux
Copy link
Contributor

Actually, scratch all that. It seems to be hanging randomly, on both macOS and Ubuntu runners, irrespective of API level. See the runs at https://github.com/marcprux/swift-nio/actions/runs/12812276673

I'm still not able to get it to hang on my local machine, but I'll keep trying.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 16, 2025

Does it always hang on the same test?

@marcprux
Copy link
Contributor

No, it seems to vary. Sometimes in NIOAsyncSequenceProducerTests.testYield_whenStreaming_andNotSuspended_andDemandMore , sometimes in NIOAsyncWriterTests.testSetWritability_whenInitial. It's hard to tell exactly because I think there's some output buffering going on (because sometimes the log will just stop at an incomplete line like:

Test Case 'NIOAsyncSequenceProducerTests.testYield_whenS
Error: The operation was canceled.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 17, 2025

Yeah, that seems fairly likely. Can you run the underlying test binary directly? Potentially running it under stdbuf -o0 to try to disable output buffering entirely.

@finagolfin
Copy link
Contributor

On my Android CI, it is entirely consistent: linux CI runs pass every time while macOS runs always hang. See the run yesterday: across two attempts with this pull, I had four macOS hangs and six linux runs pass (ignore the devel macOS failures, that's a separate issue). So much so that today I only reverted this pull for macOS, finagolfin/swift-android-sdk@7476f2be5, and all the runs passed.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 17, 2025

@finagolfin What happens if you only revert the test?

@finagolfin
Copy link
Contributor

I don't think this test is the issue, as it appears to be failing elsewhere in the log, but I will check that and let you know.

@finagolfin
Copy link
Contributor

Nope, you were right to suspect the test, as simply reverting this test on macOS got the NIO tests to pass again. 😄

Marc, can you confirm?

marcprux added a commit to marcprux/swift-nio that referenced this pull request Jan 18, 2025
@marcprux
Copy link
Contributor

Marc, can you confirm?

Confirmed: if I remove the test, then the hangs never occurs in the emulator (running on macOS-13 or Ubuntu).

@finagolfin
Copy link
Contributor

@marcprux, are you disabling all NIOAsyncWriterTests though, as shown in the linked commit above, marcprux/swift-nio@87d02310d? If so, it would be better to just disable this new test, as I did above, to see if this code change affects the older tests too, which you are no longer running.

I'm going to try running the linux-compiled tests on the macOS emulator and vice versa next, to see if that helps narrow down where this strange hang is coming from, as it could be that we're hitting a bug in the Android emulator for macOS.

@Lukasa
Copy link
Contributor

Lukasa commented Jan 20, 2025

This could be an emulator bug, but it could also be that we're seeing an ordering issue flushed out by the performance overhead of the emulator. It'd be useful to try to get a "sequence of events" log from your hanging test as well, as concurrent tests are horrible to debug.

@finagolfin
Copy link
Contributor

finagolfin commented Jan 24, 2025

Alright, I ran the NIO tests alone in the Android emulator, once swapping the mac-compiled tests into the linux-hosted emulator and vice versa, once keeping the mac-built tests in the mac-hosted emulator and so on, where only the mac version hung.

Looking at the results from the swapped run, the macos-hosted emulator again hung each time, with the linux-compiled tests not finishing for 20 minutes before I killed it (I have a 5 macOS job limit on the OSS free tier of github Actions, so I killed it once it seemed to hang, so other jobs could proceed). I just kicked off a swapped run again that I will let run for longer to make sure it does hang after longer (just killed it again after the macos-hosted emulators all hung for more than an hour and a half).

This means the problem is in the interaction between this test and the macOS-hosted Android emulator, since the linux-compiled test runs fine in the linux-hosted emulator but hangs in the mac-hosted emulator. The mac cross-compile and mac-hosted Android emulator are consistently slower on github Actions, because of greater contention for fewer macOS CI machines or whatever, so either that lack of speed screws up this test or there is a bug in the macOS-hosted Android emulator.

Regarding "a 'sequence of events' log," you want me to spray this test with print statements to figure out what's running when?

@Lukasa
Copy link
Contributor

Lukasa commented Jan 27, 2025

Yeah, that was what I was asking for. Presumably this is the result of a specific ordering sequence caused by the slowdown in the emulator.

@orobio
Copy link
Contributor Author

orobio commented Feb 4, 2025

Sorry, I was away for a while and completely missed this. I'd like to mention that I'm still a bit uncertain about one Task being blocked, waiting for another Task. Could that have anything to do with this? I can imagine that could cause a problem when other tests are doing a similar thing and we're running out of Swift concurrency threads that are making forward progress.

@finagolfin
Copy link
Contributor

No idea, I will stick a bunch of print()s in later this week and try to get you a log. You may be able to reproduce on linux yourself with slow or constrained hardware.

@orobio
Copy link
Contributor Author

orobio commented Feb 6, 2025

It'd be great to have a more detailed log, thanks! Perhaps next week or so I could have a look at trying to reproduce this on constrained hardware.

@finagolfin
Copy link
Contributor

Alright, finally got around to this, but not sure how useful it is considering the test output may not always flush.

I disabled all other tests in this test file and stuck print()s all over this new test. Most NIO test runs on the Android emulator in macOS then hang the output in random places, but two of them actually show something inside this test. You can compare those last two failing macOS runs to a passing run on linux.

Given that the test output might cut off anywhere before the actual hang, not sure how useful that is.

@orobio
Copy link
Contributor Author

orobio commented Feb 13, 2025

Thanks for trying this and the detailed report! Indeed, it's hard to say what's going on without a proper flush. I tried to reproduce this on a VM with only one CPU, but that worked fine. However, I was able to trigger a hanging state by adding a delay in one of the tasks. This causes a timeout when waiting for both yields being suspended, which results in the ConditionLock value never being set to true. The timeout is only 1 second, so maybe that one triggers in the Android emulator, causing the issue?

I created the following patch: Fix hanging NIOAsyncWriter test

It does 2 things:

  1. Increase the timeout for waiting for both yields being suspended from 1 to 5 seconds.
  2. Add a timeout to waiting for the ConditionLock, so that the test will always complete.

I'm hoping that the former fixes the issue, but if not, the latter should make sure that the test doesn't hang and we can hopefully get more information from the log. Could you give this a test run?

@finagolfin
Copy link
Contributor

I just ran it with your test modifications: doesn't hang anymore but now fails, see full log output on my CI.

@orobio
Copy link
Contributor Author

orobio commented Feb 20, 2025

It looks like what's happening is what I mentioned earlier. It's waiting on the condition lock and there is no thread available on which the other Task can be scheduled to set the condition. I am not able to reproduce this on my Linux VM with one CPU core. I did some tests and I found that when I keep creating Tasks that block on a ConditionLock, Swift just keeps spawning new threads to schedule the work on, until a maximum of 128 threads is reached. If I understand correctly, the underlying libDispatch is doing this and the Android implementation does not use libDispatch, which explains the difference. I guess the Android implementation really has a fixed-width thread pool.

@finagolfin: After some more digging, I can see that all your configurations are using 2 CPU cores for the Android emulator, but I found the following warning in your Android emulator launch logs for the MacOS runs:
WARNING | Running on a system with less than 6 logical cores. Setting number of virtual cores to 1

So, it looks like it's a combination of having only 1 CPU core and a fixed-width concurrency thread pool that causes the issue.

@Lukasa: A solution could be to add an async test hook into NIOAsyncWriter, so that we can just suspend instead of blocking the thread. For example, something like _didYieldAsync, or perhaps _willPerformAction. Would this be acceptable? Or do you know of any other way we can solve this without the need for another test hook?

@Lukasa
Copy link
Contributor

Lukasa commented Feb 20, 2025

Having an async method on the writer delegate is a weird design, because it arguably misses the intended use-case, which is to bridge async producers into synchronous consumers. The sink delegate is supposed to be synchronous because it is the consumer.

A better option is to find a way to skip the test in cases where it is likely to run into trouble. If we needed a real sledgehammer we could just skip the entire test on Android, but I wonder if we can construct a better question to ask the runtime.

@finagolfin
Copy link
Contributor

If I understand correctly, the underlying libDispatch is doing this and the Android implementation does not use libDispatch, which explains the difference.

Swift Concurrency on Android uses libdispatch, and I haven't seen any big differences in this repo either, so I don't think that's it. In fact, this test has always worked well on the Android emulator on linux and I believe Marc says it often passes locally in the Android emulator on his macOS machine.

So this is most likely tied to the slowness of the Android emulator on the github macOS CI, with the outside chance of some software interaction or bug in that environment.

looks like it's a combination of having only 1 CPU core

Could be, as I don't see that warning when starting the Android emulator on linux in that same linked CI run.

@orobio
Copy link
Contributor Author

orobio commented Feb 20, 2025

Having an async method on the writer delegate is a weird design, because it arguably misses the intended use-case, which is to bridge async producers into synchronous consumers.

I understand, but I meant for it to only be used for testing, just like the already existing didSuspend callback, which is directly set on the storage with _setDidSuspend(...). We need some way to pause execution of the first yield at the right moment, to make sure the state machine is in the correct state for the second yield. With an async hook we can add this pause without blocking a thread from the concurrency pool. The didYield call is actually performed from an async context, so it should be easy to add an async callback for testing next to it.

Swift Concurrency on Android uses libdispatch, and I haven't seen any big differences in this repo either, so I don't think that's it.

Ah, I didn't know it used libdipatch as well. Could it be that libdispatch behaves differently on Android for some reason? On my Linux machine it starts a maximum of 128 threads. When I start 127 Tasks at the start of the test and block them all, leaving exactly 1 concurrency thread free, I can see that the test fails in the same way as on the MacOS Android emulator. When I start 126 blocked Tasks, leaving 2 concurrency threads free, the test is successful. That, combined with the fact that the MacOS Android emulator runs on 1 core and the Linux one on 2 cores makes me think that it's very likely that's the issue. If you want, I can create something with which we can test the behavior of the concurrency thread pool in the Android emulator.

@Lukasa
Copy link
Contributor

Lukasa commented Feb 21, 2025

I understand, but I meant for it to only be used for testing, just like the already existing didSuspend callback, which is directly set on the storage with _setDidSuspend(...).

My nervousness there though is that didSuspend is still synchronous. While the hook may only be intended for use in testing, it's actually available for use everywhere, and any future reading of the code has to reason about it in the face of potential concurrent calls.

With that said, I'm not 100% definitely opposed. Just noting that it conceptually complicates matters somewhat.

@orobio
Copy link
Contributor Author

orobio commented Feb 25, 2025

There is another bug in the implementation that was revealed by the Android run. In the log it can be seen that an error was thrown from one of the tasks. This happens due to that the continuation of the second task (which should suspend again) will not be resumed until the yield of the first task is fully completed. The bug is that when the first yield finishes up, the state machine determines that there are no suspended yields and it moves the state to .finished, resulting in an alreadyFinished error from the second task.

I can have a look at fixing that issue later, but if we're going with the async test hook approach, I anticipate that we probably need another hook to be able to test that issue, which means that we're already looking at adding 2 more hooks. After thinking about it some more, I'm now leaning towards using a custom executor and setting an executor preference on the tasks. That way we can guarantee that there are at least 2 threads available. Later, we can use the same mechanism to make sure there is only 1 thread available for both tasks, which will reproduce the issue described above.

I'm not really familiar with custom executors, but I think this can work and will investigate it more.

@finagolfin
Copy link
Contributor

Could it be that libdispatch behaves differently on Android for some reason?

It could be, but I doubt it, especially since this test always passes in the Android emulator run on linux, which is why I only disabled it on the Android emulator run on macOS.

That, combined with the fact that the MacOS Android emulator runs on 1 core and the Linux one on 2 cores makes me think that it's very likely that's the issue.

Yes, that core count difference you found is more likely the culprit.

If you want, I can create something with which we can test the behavior of the concurrency thread pool in the Android emulator.

Sounds good, I will also try limiting the core count of the emulator running on linux and see if that reproduces this hang.

@orobio
Copy link
Contributor Author

orobio commented Feb 28, 2025

I created a test to see the behavior of the concurrency thread pool.

When I run only this test on my single core Linux VM, it results in:

Detected initial number of threads: 1
Detected maximum number of threads: 128

And on my 8 core Linux VM:

Detected initial number of threads: 8
Detected maximum number of threads: 128

When I run the complete test suite, the initial number of threads count sometimes goes up a bit and sometimes it doesn't. I've seen it go up to around 30 on both machines.

I'm very curious to see what will happen on the Android runs!

@finagolfin
Copy link
Contributor

I think you've managed to track this down, the number of cores appears to be the cause. First, I applied your patch with the new test and ran it on the linux and mac Android emulators as usual. The linux-hosted emulator passed as always:

Test Case 'NIOAsyncWriterTests.testConcurrencyThreadPoolWidth' started at 2025-03-03 11:21:29.021
2025-03-03 11:21:29 +0000: Starting Task 0
2025-03-03 11:21:29 +0000: Task 0 started
2025-03-03 11:21:29 +0000: Starting Task 1
2025-03-03 11:21:29 +0000: Task 1 started
2025-03-03 11:21:29 +0000: Starting Task 2
Detected initial number of threads: 2
Detected maximum number of threads: 2
2025-03-03 11:21:39 +0000: Task 2 started
Test Case 'NIOAsyncWriterTests.testConcurrencyThreadPoolWidth' passed (10.01 seconds)

while the mac-hosted emulator hung:

Test Case 'NIOAsyncWriterTests.testConcurrencyThreadPoolWidth' started at 2025-03-03 11:33:12.245
2025-03-03 11:33:12 +0000: Starting Task 0
2025-03-03 11:33:12 +0000: Task 0 started
2025-03-03 11:33:12 +0000: Starting Task 1
Detected initial number of threads: 1
Detected maximum number of threads: 1
2025-03-03 11:33:22 +0000: Task 1 started

I then limited the linux-hosted emulator to a single core just like the mac and they all hung:

Test Case 'NIOAsyncWriterTests.testConcurrencyThreadPoolWidth' started at 2025-03-03 12:38:48.854
2025-03-03 12:38:48 +0000: Starting Task 0
2025-03-03 12:38:48 +0000: Task 0 started
2025-03-03 12:38:48 +0000: Starting Task 1
Detected initial number of threads: 1
Detected maximum number of threads: 1
2025-03-03 12:38:58 +0000: Task 1 started

@orobio
Copy link
Contributor Author

orobio commented Mar 4, 2025

Great, that definitely shows the problem. I will investigate the use of a custom task executor further and can hopefully provide a fix based on that.

This indeed shows different behavior between (GNU) Linux and Android. Would it make sense to align them? I've always understood the Swift global concurrency thread pool to be a fixed width pool, so from that perspective the behavior on Android is correct. However, I can imagine that changing the behavior on Linux would have some undesired impact on existing projects.

One thing that bothers me is that the test hangs when a single core is used. I've been staring at the code for a while, but I don't see why that would happen. If you have any ideas, I'd be happy to hear them!

orobio added a commit to orobio/swift-nio that referenced this pull request Mar 7, 2025
…ndroid emulator

The test requires at least two threads in the concurrency thread pool
because it blocks one task, which waits for another task to set a condition.
In environments where the global concurrency thread pool doesn’t have at
least two threads available, the test will fail, as observed on the Android
emulator running with a single virtual core (see discussion in apple#3044).

Using a custom task executor guarantees that at least two threads are
available for the test, regardless of the width of the global concurrency
thread pool.
orobio added a commit to orobio/swift-nio that referenced this pull request Mar 7, 2025
…ndroid emulator

The testSuspendingBufferedYield_whenWriterFinished test requires at least
two threads in the concurrency thread pool because it blocks one task,
which waits for another task to set a condition. In environments where
the global concurrency thread pool doesn’t have at least two threads
available, the test will fail, as observed on the Android emulator running
with a single virtual core (see discussion in apple#3044).

Using a custom task executor guarantees that at least two threads are
available for the test, regardless of the width of the global concurrency
thread pool.
orobio added a commit to orobio/swift-nio that referenced this pull request Mar 11, 2025
…ndroid emulator

The testSuspendingBufferedYield_whenWriterFinished test requires at least
two threads in the concurrency thread pool because it blocks one task,
which waits for another task to set a condition. In environments where
the global concurrency thread pool doesn’t have at least two threads
available, the test will fail, as observed on the Android emulator running
with a single virtual core (see discussion in apple#3044).

Using a custom task executor guarantees that at least two threads are
available for the test, regardless of the width of the global concurrency
thread pool.
orobio added a commit to orobio/swift-nio that referenced this pull request Mar 23, 2025
…ndroid emulator

The testSuspendingBufferedYield_whenWriterFinished test requires at least
two threads in the concurrency thread pool because it blocks one task,
which waits for another task to set a condition. In environments where
the global concurrency thread pool doesn’t have at least two threads
available, the test will fail, as observed on the Android emulator running
with a single virtual core (see discussion in apple#3044).

Using a custom task executor guarantees that at least two threads are
available for the test, regardless of the width of the global concurrency
thread pool.
orobio added a commit to orobio/swift-nio that referenced this pull request Mar 23, 2025
…ndroid emulator

The testSuspendingBufferedYield_whenWriterFinished test requires at least
two threads in the concurrency thread pool because it blocks one task,
which waits for another task to set a condition. In environments where
the global concurrency thread pool doesn’t have at least two threads
available, the test will fail, as observed on the Android emulator running
with a single virtual core (see discussion in apple#3044).

Using a custom task executor guarantees that at least two threads are
available for the test, regardless of the width of the global concurrency
thread pool.
glbrntt added a commit that referenced this pull request May 12, 2025
…#3135)

### Motivation:

The testSuspendingBufferedYield_whenWriterFinished test fails on the
Android emulator. See also the discussion in #3044.

### Modifications:

The test requires at least two threads in the concurrency thread pool
because it blocks one task, which waits for another task to set a
condition. This PR adds support for running a task executor based on a
NIOThreadPool and uses it for the test. Using a custom task executor
guarantees that at least two threads are available for the test.

Additionally, the test has been renamed to
testWriterFinish_AndSuspendBufferedYield, which is more in line with the
other test names.

### Result:

The test will pass regardless of the width of the global concurrency
thread pool.

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
Co-authored-by: Cory Benfield <lukasa@apple.com>
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.

4 participants