Repository navigation
Conversation
…_action Integrates libcurl's multi socket interface with the cats-effect FileDescriptorPoller (epoll/kqueue), so no thread blocks on network I/O. Requests are spread over several multi handles in multi-threaded mode.
…hronized A monitor blocks compute worker threads under contention. A fiber-aware Mutex suspends the waiting fibers instead.
Contributor
Author
|
Closing for now, will reopen when ready. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two asynchronous Scala Native curl backends which never block a thread on network I/O:
CurlCatsAsyncBackend(catsmodule):Backend[IO].CurlFs2Backend(fs2module):StreamBackend[IO, Fs2Streams[IO]], additionally supporting streaming response bodies (with backpressure) and stream request bodies (buffered in memory).Both are created as a
Resource(CurlCatsAsyncBackend.resource(),CurlFs2Backend.resource()).How it works
curl_multi_socket_action+ socket/timer callbacks). The sockets curl waits on are registered with cats-effect'sFileDescriptorPoller(epoll on Linux, kqueue on macOS); curl's timeouts useIO.sleep. curl shares the event loop with the rest of the application.Mutex[IO](waiting fibers are suspended, no thread is blocked), held only for the short non-blocking libcurl calls. Requests are spread over one multi handle per available processor in multi-threaded mode (one otherwise).socket_actionthe socket is re-checked withpoll(fd, 0)and curl is driven while it stays ready.CURL_WRITEFUNC_PAUSE, and the transfer is resumed withcurl_easy_pauseonce the consumer drains it below ~64 KB.FileDescriptorPollerin the runtime (the defaultIORuntimehas one on Linux and macOS, not e.g. on Windows): creating the backend fails with CE's "No FileDescriptorPoller installed" error otherwise. There is no blocking fallback.catsNativegetscats-effect(not only kernel/std) as a compile dependency, asFileDescriptorPolleris reached throughIO.pollers.Changes to the shared curl code
GenericCurlBackend[F, P](parametrized by the capabilities) with hooks for streaming responses and stream request bodies;AbstractCurlBackendextends it with no capabilities.cleanupCurlhook: easy handles sharing a multi handle must not be cleaned up concurrently with other calls into it (this caused crashes). Handles are now also cleaned up (and the response buffers freed) when a request fails or is cancelled; previously they leaked.Bugs fixed in the shared code (found by running the real test suites, see below), which also affect the existing curl backends:
sendallocated the easy handle, zone and headers when called, not when the returned effect is run, and freed them after the first run. Running an effect twice (retries) or in parallel used freed memory. It is now lazy.String: the declaredContent-Lengthdidn't match what was sent, which corrupted keep-alive connections (the next request on the connection failed). Bodies are now sent as exact bytes with an explicit size.Tests
ScalaTest on Scala Native ignores the result of tests which return a
Future: a failing assertion inside aFuturedoesn't fail the test (evenFuture.successful(1).map(_ shouldBe 2)passes). So the existing IO-based Native suites, includingCurlCatsHttpTest, never actually verified assertions.BlockingConvertToFuture(test code) runs theIOto completion and fails synchronously; I checked that it catches deliberately wrong assertions. It is applied toCurlCatsHttpTest,CurlCatsAsyncHttpTest,CurlFs2HttpTestandCurlFs2StreamingTest.For
HttpTeston Native, two adjustments are needed (CurlHttpTestOverrides):shouldBe Symbol("empty")needs reflection, and per-part multipart charsets aren't supported (supportsCustomMultipartEncoding = false).New:
CurlCatsAsyncHttpTest,CurlFs2HttpTest,CurlFs2StreamingTest(the sharedStreamingTest, plus a 4 MB response consumed slowly, which pauses and resumes the transfer).Docs
docs/backends/native/curl.md: sections for both backends and their limitations.Limitations
asInputStreamstill uses the blockingcurl_multi_perform/curl_multi_pollimplementation.Open questions
cats-effectto thecatsmodule (and the fs2 backend to thefs2module)?fs2already addsfs2-iofor Native, so I followed that.Verification
Run on the fork (
lolgab/sttp): compile and tests pass on all Scala versions × JVM/JS/Native, including the Native suites on Linux (epoll). The only failing CI step on the fork isPrepare release noteson JDK 17, because the fork's default branch has no.github/release-drafter.yml.