Commit Graph
766 Commits
Author SHA1 Message Date
Jacek Sieka 890687e7e0 Merge branch 'master' into asyncdispatch_stacktrace_port 2026-06-17 09:16:13 +02:00
Shuu 1f2151316a docs(asyncengine): add contains stub to nimdoc branch (#657)
The `when defined(nimdoc)` stub branch omitted `contains`, which
threadsync.nim calls via `loop.contains(fd)`, so `nim doc` failed for
modules importing chronos/threadsync.
2026-06-16 11:53:23 +00:00
Jacek Sieka d690a4915f timer: remove redundant comparisons (#658)
these get synthesized by `system`
2026-06-16 10:49:37 +00:00
Eugene KabanovandJacek Sieka 5c50952dcc Remove symbols which were deprecated 6 years ago. (#564)
Co-authored-by: Jacek Sieka <jacek@status.im>
2026-06-16 10:47:44 +00:00
Jacek Sieka 34d193061e httpclient: add CONNECT proxy / tunnel support (#656)
Using `tunnel`, a general TCP tunnel can be created for any protocol via
a HTTP proxy. This tunnel is typically used for establishing TLS
connections via a proxy but any protocol can be used.

When a tunnel is established, the connection is removed from the session
pool and given to the caller who is now responsible for closing it.

Because the tunnel connection itself can be encrypted, we return an
`AsyncStream` that the caller must close (rather than a
`StreamTransport`).

This PR also moves DNS name resolution to the connection provider - this
has the effect of delaying DNS lookup until the actual connect attempt
is made - earlier, DNS lookups would happen when creating the HTTP
address. The move allows the proxy to perform the name resolution when
relevant - the move also prepares the API for async name resolution.

Callers can still skip name resolution by providing an explicit list of
addresses to connect to.
2026-06-16 10:20:46 +00:00
Jacek Sieka 3c5654c990 http: align with RFC 9112, deprecate pipelining (#653)
The pipelining implementation was incomplete insofar as it only enabled
connection reuse without actually performing pipelining - keep the reuse
but deprecate pipelining.

With pipelining cleaned up, several other RFC 9112-related cleanups can
also be performed:

* clarify "perstistent connections" vs pipelining, where relevant
* in HTTP/1.1, don't send `Connection: keep-alive` (this is the default
in 1.1) - similarly, don't send `Connection: close` in other versions
* in older HTTP versions, disable persistent connections / keep-alive
entirely following RFC recommendations
* decode message body length according to RFC 9112
* prioritise `Transfer-Encoding` over `Content-Length`, per RFC 9112
* document lack of EOF monitoring when using persistent connections
* differentiate "no body" from "body length" tracking, ie a HEAD request
never has a body but might send a Content-Length for use as a length
discovery mechanism
* streamline request body sending code to reuse stream helpers
* fix sending and parsing of CONNECT authority form
* fix processing of `Connection` header - in particular, `Connection:
Keep-Alive` should not be used in HTTP/1.1 since persistent connections
are the default - instead, `Connection: close` should be used when the
connection is _not_ perisistent.
* exclude fragment from request-target
2026-06-16 11:12:14 +02:00
Jacek Sieka b76a78f1da http: add absolute request-target support (fixes #193) (#651)
Using an absolute request URI and a connection provider that connects to
a different server than the http address, generic proxies can be
implemented

* add detail to http connection error
* fix transport/stream leak when http client construction fails
2026-06-15 20:23:04 +02:00
Chrysostomos Nanakos 9620a4691a fix(asyncengine): drain idle callbacks based on event count, not buffer size (#655)
Signed-off-by: Chrysostomos Nanakos <chris@include.gr>
2026-06-11 07:56:46 +00:00
Jacek Sieka e4ba8cd1a4 stream: fix close regression (#652)
`close` should not be forwarded to the wrapped stream (it's needed for
keep-alive in http)
2026-06-03 09:39:52 +00:00
Jacek Sieka e9eadbfcbe selector: dynamically adjust event count (#637)
When fetching events with epoll/kqueue, we currently use a fixed size
array to fetch system events.

Taking an idea from
[libevent](https://github.com/libevent/libevent/blob/48296514d8fd9c0b3812b11d45ad80b0c002c14e/epoll.c#L568),
we can instead dynamically grow the number of events fetched as the
system comes under load thus reducing the number of poll calls, allowing
more work to be done per batch.

The change also has the effect that data that already arrived at the
time of the poll call gets processed before timers are fired - this
means that timeouts are less likely to be triggered unfairly due to
event queueing order and the time it takes to process each event.

* increase max events per loop to 4096 (same as libevent)
* reduce initial event list allocation to 32
* get rid of `ReadyKey.errorCode` which is unused
2026-06-01 12:59:56 +00:00
Jacek Sieka 04e770f18e streams: cleanup (#649)
* streams: cleanup

* allow and more consistently handle zero-byte writes across
stream/transport
* remove redundant `read`/`consume` from stream vtable
* remove `toUnchecked` untyped template (not part of transport
interface!)
* make `AsyncStreamState` pure
* reset stream queue when done (releases memory a little earlier)
* fix cancel callback defect on some windows accept errors
* fix docs for `read(n)` (we'll read fewer than `n` bytes in case of
EOF)

* disable orc tests on 2.0

* verbose

* all orc tests >=2.2
2026-06-01 11:57:21 +02:00
Jacek Sieka 31ddf9be65 streams: replace queue+loop with vtable (#644)
The current approach of specializing streams for specific protocols
relies on each stream operating a buffer/queue and a read/write loop
that replenishes or drains these.

For some protocols such as TLS, a buffer is naturally required since
we're mutating the data along the way (in this case by encrypting it) -
for others, such as chunking or limiting, the data remains unchanged and
all the stream does is to either frame the data or provide limits and
safety nets - for such streams, the buffer approach carries significant
overhead - using a read as an example:

* a read loop starts reading from an underlying data source
* when data arrives, data is copied to a buffer
* when application wants to read, it checks the buffer and copies data
to user buffer
* if data is not available, it waits for the buffer to be replenished by
the read loop

In the above case, every byte must be copied twice and we have to wait
an additional loop iteration for each stream layer as the loop wakes the
actual reader.

Futher, each layer requires memory for the buffer - using a small buffer
means high latency due to the extra event loop round trips while a large
buffer is wasteful since most of the time, it's not actually being used
(ie while waiting for data from a socket).

The API of streams is fairly complex - operations such as `readLine` and
`readUntil` also require buffering since they must "look ahead" in the
data to find separators and at the same time not "overread" their
supporting source stream.

However, other operations do not require this extra buffering -
`readOnce` for example is already given a buffer to write to and all it
needs to do is continue using this buffer, similar as happens with [bulk
reads](https://github.com/status-im/nim-chronos/pull/632).

Similarly, when servicing a `write`, we do not actually need to copy
the data into a queue - all that's needed is that the data remains
available throughout the write operation, a guaranteee conveniently
provided by `async` already.

All in all, this means that for simple reads and writes, there is no
inherent need to do buffering - the buffering is merely a side effect of
the loop-based implementation.

By replacing the loop with a vtable, we can specialize the operations
that a stream based on the specific requirements of the stream protocol
being implemented instead of forcing them all to use a buffer.

This allows implementations that don't require a buffer to specialize
each operation according to its specific needs - a chunked reader for
example can pass the user buffer directly to the next layer, and if the
next layer is a socket, it means that the read is done directly into the
user buffer for the majority of data. Using knowledge about the limits,
it can also pre-allocate a target buffer of the correct size instead
of incrementally building it up.

A common structure of protocols is that they start with a header and
then perform bulk transfers - for these protocols, we can use the stream
buffer for the header read and perform the rest of the transfer directly
to the user buffer.

The effect is significant - here's the http benchmark (http is a heavy
user of streams):

Pre:
```
| Small/small    |    0.063s |   1000 | 15817.438 |    0.031 MB |    0.016 MB |    0.483 MB/s |    0.256 MB/s |
| Medium/small   |    0.845s |   1000 | 1182.766 | 1000.000 MB |    0.021 MB | 1182.766 MB/s |    0.025 MB/s |
| Small/Medium   |    1.045s |   1000 |  956.632 |    0.031 MB | 1000.000 MB |    0.029 MB/s |  956.632 MB/s |
| Medium/Medium  |    1.286s |   1000 |  777.588 | 1000.000 MB | 1000.000 MB |  777.588 MB/s |  777.588 MB/s |
```

Post:
```
| Small/small    |    0.073s |   1000 | 13774.390 |    0.031 MB |    0.016 MB |    0.420 MB/s |    0.223 MB/s |
| Medium/small   |    0.312s |   1000 | 3200.978 | 1000.000 MB |    0.021 MB | 3200.978 MB/s |    0.067 MB/s |
| Small/Medium   |    0.537s |   1000 | 1862.990 |    0.031 MB | 1000.000 MB |    0.057 MB/s | 1862.990 MB/s |
| Medium/Medium  |    0.880s |   1000 | 1136.555 | 1000.000 MB | 1000.000 MB | 1136.555 MB/s | 1136.555 MB/s |
```

* deprecate `udata` from streams - this is not being used anywhere and
  just creates surface area for bugs
* deprecate `loop`-based streams
* move all streams to vtable-based approach, avoiding `if`-based
  implementation selection
* keep readUntil/readLine implementation close to the buffer
* maintain atomicity of `write` calls with `AsyncLock` instead of a
  queue

Once these foundational changes are done, there are a few more things
that can be improved:

* HTTP streams can be further simplified - in particular, the bounded
  stream serves many masters and could be split into a "byte-limiting"
  stream and a "readUntil"-limiting stream so that the implementation
  of each is simpler, composing them instead.
* TLS streams use a trick to simplify the implementation: instead of
  reading and writing concurrently, a read is initiated but all writes
  are processed before that read is awaited - the two operations could
  be made fully concurrent with some additional implementation
  complexity
* TLS streams are documented not to perform `close_notify` since this
  would require expanding the API to provide a notion of "full duplex
  close", something that the split reader/writer streams don't do
  well (in TLS 1.3, the protocol has been upgraded to support half-
  closed streams which would fit the model better)
* Closing sequences could be simplified - in particular, the concept
  of ownership should be introduced so that closing a "parent" stream
  also closes the underlying source, when this is desireable.
  Similarly, finishing a write stream should lead to `SHUT_WR` being
  sent on the wire making the underlying TCP closing sequence more
  predictable and efficient.
* Parts of the stream API can be simplified or removed - for example,
  there's no need to maintain so many "states" since we no longer
  need to track the activity of a "loop" that no longer exists.
* Potentially, buffered operations could be moved out from the API
  entirely and/or offered only by certain stream types, dividing the
  VTable into a "simple" unbuffered subset and the full buffered
  set of operations.
2026-05-11 09:16:02 +00:00
Jacek Sieka 7396b60662 tests: more tls tests (#643) 2026-05-05 14:59:05 +00:00
Shuu c7c1dca747 Add ALPN support to TLSStream (#640)
* Add ALPN support to TLSStream

* Replace {.emit.} workaround with sslEngineSetProtocolNames

* Bump bearssl to 0.2.8
2026-05-05 14:14:24 +02:00
Andrii Fil 43a9c42546 Update examples.md (#648) 2026-05-05 09:25:35 +02:00
Jacek Sieka 12183fb32f http: move body into post request (#645)
* also fixes ambiguous `post` overloads when not using a `body`
2026-05-04 10:31:12 +02:00
markspanbroek 7cc1ad8079 fix(httpclient): pipelining session handles closed connections (#646)
Before this change, HttpSession tried to reuse connections that
were already closed by the server, leading to stream reading
failures.
2026-05-04 09:45:30 +02:00
Jacek Sieka 3bc3d80256 http: avoid some copies (#642)
While waiting for:

* https://github.com/status-im/nim-chronos/issues/601
* https://github.com/status-im/nim-chronos/issues/578
* https://github.com/nim-lang/Nim/issues/25057

In particular, the move-on-return are ugly but needed for #639 to do its
job well
2026-04-09 17:53:25 +02:00
Eugene Kabanov b411dd632b Fix TLSStream exception not generated after TLS errors. (#641)
* Fix TLSStream TLSStreamProtocolError exception not generated after TLS checks.
Add more tests.
Update RSA self-signed TLS certificate with CNAME = chronos-test-server.com

* Recover old RSA keys.

* Address review comments.
2026-04-08 14:17:57 +00:00
Jacek Sieka 9dc60668bc future: another sink attempt (#639)
Allowing `complete` to `sink` values avoids the compiler to move values
into the future which can avoid copies in a few cases:

Previous attempts have been reverted due to
https://github.com/nim-lang/Nim/issues/23354.

Incidentally, we can also remove the workaround for
[literals](https://github.com/nim-lang/Nim/issues/22175) since we gate
the usage of sink from 2.0.6.
2026-04-08 08:18:58 +02:00
Jacek Sieka 6080fef1b2 Speed up http client (#633)
The http client currently uses an inefficient byte-by-byte copy in its
`BoundedStreamReader` - the real solution here is to get rid of the
copy completely but until then, we can use bulk-copy the data at least.

Ditto `read`, `readN` and similar helpers - this ~doubles throughput
for bulk reading.

Pre:
```
| Small/small    |    0.070s |   1000 | 14226.009 |    0.031 MB |    0.016 MB |    0.434 MB/s |    0.231 MB/s |
| Medium/small   |    1.834s |   1000 |  545.267 | 1000.000 MB |    0.021 MB |  545.267 MB/s |    0.011 MB/s |
| Small/Medium   |    2.238s |   1000 |  446.735 |    0.031 MB | 1000.000 MB |    0.014 MB/s |  446.735 MB/s |
| Medium/Medium  |    2.583s |   1000 |  387.196 | 1000.000 MB | 1000.000 MB |  387.196 MB/s |  387.196 MB/s |
```

Post:

```
| Small/small    |    0.066s |   1000 | 15038.890 |    0.031 MB |    0.016 MB |    0.459 MB/s |    0.244 MB/s |
| Medium/small   |    0.954s |   1000 | 1048.475 | 1000.000 MB |    0.021 MB | 1048.475 MB/s |    0.022 MB/s |
| Small/Medium   |    1.264s |   1000 |  791.318 |    0.031 MB | 1000.000 MB |    0.024 MB/s |  791.318 MB/s |
| Medium/Medium  |    1.615s |   1000 |  619.083 | 1000.000 MB | 1000.000 MB |  619.083 MB/s |  619.083 MB/s |
```
2026-04-07 19:37:35 +02:00
Jacek Sieka 539767ce2d stream: Bulk reads (fixes #571) (#632)
Bulk reads allow data received from `StreamTransport` to be copied
directly to a user-supplied buffer, bypassing the transport buffer and
callback mechanism.

For large buffers, this increases throughput by almost 3x:

Pre:
```
3244.356 MB/s 209103.971 reads/s 16269.200 bytes/read
```

Post:
```
11181.966 MB/s 184897.441 reads/s 63414.298 bytes/read
```

Latency is also reduced significantly under load (due to fewer yields
per read) and the transport buffer can be smaller (or zero for
applications that do their own buffering).

Using `readExactly` as an example:

* `transp.buffer` is empty - `reader` future is set up,  `resumeRead` is
called
* control yields to the chronos (even if the socket has data)
* data becomes available, selector wakes callback and `recv` is called
copying the data to `transp.buffer`, up to the size of the buffer
* `reader` future is completed, control yields to chronos
* `readExactly` resumes, copies `transp.buffer` to user buffer
* repeat

The new flow instead performs the following operations:

* `recv` is called in `readExactly` writing directly to the user buffer
until `EWOULDBOCK`
* `resumeRead` is called to wait for more data
* `recv` is called in the callback, writing directly to the user buffer
* `readExactly` resumes
* repeat

In the case that there is data already in the socket, `readExactly`
returns at once without 2 more loop iterations.

In the case that there is lots of data in the socket, fewer calls to
`recv` are needed since the user buffer already is as large as the
expected amount of data.

In the case there is no data, this process results in an extra `recv`
call (to discover `EWOULDBLOCK`) compared to the status quo.

Bulk reads are only used when the user buffer size exceeds transp.buffer
- this means:

* For "small message" protocols, we continue using `transp.buffer` as
before
* For "header+message" protocols the header and beginning of message
will be read with a single `recv` call (assuming it fits in
`transp.buffer`), then we switch to bulk reading mode.

Other misc changes:

* also treat EAGAIN as EWOULDBLOCK (on systems where they differ)
* add thread-to-thread throughput benchmark
* clarify windows TODO
2026-04-07 15:50:05 +02:00
ShuuandJacek Sieka 3e7b228356 Add client certificate authentication support to TLSStream (#631)
Add support for mTLS client certificate authentication
in `newTLSClientAsyncStream`. Both RSA and EC key types are supported.

* Add EC test

---------

Co-authored-by: Jacek Sieka <jacek@status.im>
2026-04-07 13:42:58 +00:00
Jacek Sieka 211a22dd23 Simplify stream transport states (#630)
* `ReadClosed`/`WriteClosed` -> `Closed` (they're always set together)
* `atEof` doesn't need to check transport state
* remove redundant while looping in `readStreamLoop` (it's done by
`handleEintr`)
* move error handling to `resumeRead` / `resumeWrite` and make usre it
matches loop version
* clear reader future on read cancellation (no need to hold on to
memory)
* document closeFd
2026-04-07 15:01:33 +02:00
Eugene Kabanov d3a5959827 Set epoll engine as default for android (#636)
* Set `epoll` engine as default for android, keep `poll` engine default for `emscripten`.

* Address review comments.
2026-04-07 15:48:14 +03:00
Jacek Sieka 495cf9b1ce tests: more asyncTest usage (#638) 2026-04-07 06:39:05 +00:00
Eugene Kabanov 5230cd66ae Fix posix function declarations for sendfile. (#635) 2026-04-05 14:02:37 +03:00
Constantine MolchanovandJacek Sieka 82ecd2248d Add the book update guide (#620)
* Update mdbook-open-on-gh version.

2.4.1 would crash during build locally.

* Fix path to async_procs.md.

* Add the guide to update the book.

---------

Co-authored-by: Jacek Sieka <jacek@status.im>
2026-04-01 17:02:12 +02:00
Jacek Sieka ec3c85f132 stream test: refactor to use asyncTest (#634) 2026-03-31 21:18:20 +00:00
Constantine Molchanov 6d89155294 Generate unique symbol for returned Future in async macro. (#621) 2026-03-27 15:06:21 +01:00
Jacek Sieka 45f43a9ad8 v4.2.2 (#626)
* Compatibility with BearSSL 0.2.7
v4.2.2
2026-03-26 11:48:43 +01:00
Etan Kissling d011de1a30 Bump bearssl to 0.2.7 (#624)
`nim-bearssl` signature for `pemDecoderSetdest` was updated to `csize_t`
to avoid issues with Clang 15:

- https://github.com/status-im/nim-bearssl/pull/69

Update to the correct callback proc type to remain compatible:

- https://github.com/nim-lang/Nim/issues/25617
2026-03-24 14:07:06 +01:00
Jacek Sieka 0d00279e67 v4.2.0 (#615)
Notable features include:

* Continuous and discrete replenishment modes for reate limiter
* `AsyncSemaphore` - similar to an `AsyncLock` but can be acquired
multiple times
* `cancelAndWait` that accepts multiple futures

In addition, there are several smaller efficiency improvements and
bugfixes.
v4.2.0
2026-01-27 19:20:11 +01:00
Ivan FB 517fc03f5b fix compile error ioselector_poll setLen(0) instead clear() (#616)
* fix compile error ioselector_poll setLen(0) instead clear()

* Use of reset() instead of setLen(0) to clear the underlying data
2026-01-27 17:50:19 +00:00
Jacek Sieka 265236724a Simplify race/one implementations (#603)
Semantically, these two functions behave the same though race offers an
additional overload for heterogenous futures (and had a less expressive
return type - fixed here).

* fix `race` leak where callback would not be removed from unfinished
futures
* reduce memory allocations for varargs version
* return full future type in `race` when possible

In the future, we may want to deprecate either `one` or `race` since
they are redundant at this point.
2026-01-23 13:08:59 +01:00
Jacek Sieka 673ad0094c TCP server example (#478) 2026-01-19 16:55:36 +01:00
Jacek Sieka 712f9937e4 fix readLine for partial separator matches (fixes #573) (#605)
Also allows the separator to be empty, in which case characters are
returned one by one
2026-01-12 09:02:21 +01:00
Constantine Molchanov 5b50ddc22d CI: Doc: Replace actions-rs/install with cargo-bins/cargo-binstall. (#612) 2026-01-12 09:02:01 +01:00
Jacek Sieka 90085c31ce add missing cast in wrapAsyncSocket (#609) 2026-01-07 08:53:38 +01:00
Jacek Sieka 8ed098ef61 ratelimit: fix non-deterministic start time in test (#608) 2025-12-27 16:13:32 +01:00
Jacek Sieka 8764b3c0f2 fail should not accept CancelledError (#606)
Cancellation errors are due to `cancel` and shouldn't be set explicitly
2025-12-27 11:14:43 +01:00
NagyZoltanPeterandJacek Sieka 85af4db764 Token bucket unification (#582)
* Fix original TokenBucket, consolidate the behaviors of chronos/TokenBucket and waku/TokenBucket (compensating) with no interface change

* TokenBucket extended with waku's strict mode replenish, that does not allow refill just after period boundary elapsed.

* Adjust for new chat-sdk needs, added setState and getAvailableCapacity with small refactoring

* Better comments

* rename for better self explain code, added more explanatory comments upon code review finding

* Document TokenBucket with detailed samples of Balanced mode replenishment algorithm, extended TokenBucket unit test

* Address review comments, renaming the replenish modes, removed old/new algo comparison from documentation, fix Discrete mode update calculation to properly calculate correct last update time by period distance calculation.

* protect from devide by zero, code style fix

* Make (re)setState close pending request before reseting state

* Polishing interface, move discrete mode initial start time setup into ctor and remove resetState to have a cleaner and consistent interface for both usage mode

* Addressing review observation, keep only singel new ctor and leave defaults to match former use.

* Removing leftovers and confirm with coding guideline

* refactor rate limiter implementation

* make sure that `tryConsume` respects queued `consume` requests
* refill tokens the same way regardless of replenish mode and source of
tokens (manual/time-based/cancellation)
* whem manually replenishing, compute budget cap after satisfying queued
requests
* don't run worker if fill duration is 0
* add some docs

* int vs int64

* Fix Discrete mode time window drift, added tests

* wait for sleeper/waiter

---------

Co-authored-by: Jacek Sieka <jacek@status.im>
2025-12-12 15:25:45 +01:00
nitely 994840503f wip 2025-11-29 22:19:19 -03:00
nitely 543e731859 Port asyncdispatch stacktraces improvement 2025-11-29 21:56:55 -03:00
Jacek Sieka 40cf92bfdb asynclock: switch to deque (#596)
In case there are many waiters, this helps preserve O(1) performance -
also clear out some cancelled waiters more aggressively, in case they
appear in order.
2025-11-26 21:54:41 +01:00
Eugene Kabanov f3e582b87b Revert "chore: unnecessery assign (#599)" (#600)
This reverts commit 4dcb297ee8.
2025-11-19 18:12:40 +00:00
vladopajic 4dcb297ee8 chore: unnecessery assign (#599) 2025-11-19 14:04:51 +00:00
vladopajic dd21dd4a19 feat: add AsyncSemaphore (#586) 2025-11-19 12:03:20 +00:00
Eugene Kabanov a737147849 Fix Windows does not properly handle 0-size UDP datagrams. (#598)
Add test.
2025-11-17 12:57:18 +02:00
Jacek Sieka 6c6f86b21b move post buffer when sending (#595)
* move post buffer when sending

this helps clear `request.buffer` after sending

* remove unused cancellation handlers
2025-11-12 18:35:13 +02:00