Releases: ponylang/courier
0.2.1
Add on_timer_failure callback
HTTPClientLifecycleEventReceiver has a new on_timer_failure() callback. It fires when a user timer created by HTTPClientConnection.set_timer() cannot be armed because its ASIO event subscription failed — typically from a kernel resource error like ENOMEM on kevent or epoll_ctl. Previously, the timer was silently cancelled and applications had no way to learn it had failed.
Before the callback fires, the timer has already been cancelled and its token cleared. The connection itself continues running. The application decides how to recover — call set_timer() again to try a new timer, close the connection, or take some other action.
actor MyClient is HTTPClientConnectionActor
// ...
fun ref on_timer_failure() =>
// The query timer never armed. Give up on this request.
_http.close()The callback has a default no-op implementation, so existing receivers keep the silent-cancel behavior until they override it.
[0.2.1] - 2026-04-15
Added
- Add on_timer_failure callback (PR #49)
0.2.0
Fix potential connection hang when timer event subscription fails
On some platforms, if the operating system cannot allocate resources for a connection timer (e.g., ENOMEM on kqueue or epoll), connections could hang silently instead of reporting an error. Timer subscription failures are now detected and reported as connection failures.
Add ConnectionFailedTimerError to ConnectionFailureReason
ConnectionFailureReason now includes ConnectionFailedTimerError, which is reported when the connect timer's ASIO event subscription fails. If you match exhaustively on ConnectionFailureReason, you'll need to add a handler for the new variant:
match reason
| ConnectionFailedDNS => ...
| ConnectionFailedTCP => ...
| ConnectionFailedSSL => ...
| ConnectionFailedTimeout => ...
| ConnectionFailedTimerError => ...
endRequire ponyc 0.63.1 or later
courier now requires ponyc 0.63.1 or later. Older ponyc versions are no longer supported.
[0.2.0] - 2026-04-12
Fixed
- Fix potential connection hang when timer event subscription fails (PR #48)
Changed
0.1.5
Fix connection stall after large request with backpressure
Connections could stop processing incoming data after completing a large write that triggered backpressure, causing the connection to hang. Updated the lori dependency to 0.13.1 which fixes the underlying issue.
[0.1.5] - 2026-04-07
Fixed
- Fix connection stall after large request with backpressure (PR #47)
0.1.4
Fix crash when closing a connection before initialization completes
Calling dispose() on a connection actor before its internal initialization completed could crash. This was a rare race condition where dispose() from an external actor arrived before the connection finished setting up, since Pony's causal messaging provides no ordering guarantee between different senders. The race was unlikely but was observed on macOS arm64 CI.
[0.1.4] - 2026-03-28
Fixed
- Fix crash when closing a connection before initialization completes (PR #43)
0.1.3
Expose one-shot timer API
One-shot timers are now available on HTTPClientConnection for response deadlines and application-level timeouts.
Call set_timer() with a duration to start a timer, and override on_timer() to handle it when it fires. Cancel with cancel_timer() if you no longer need it. Only one timer can be active per connection at a time.
Unlike idle timeout, this timer fires unconditionally — I/O activity does not reset it. The typical use is a response deadline: set a timer after sending a request, cancel it when the response arrives, close the connection if the deadline fires first:
fun ref on_connected() =>
_http.send_request(Request.get("/").build())
match lori.MakeTimerDuration(5_000)
| let d: lori.TimerDuration =>
match _http.set_timer(d)
| let t: lori.TimerToken => _timer = t
| let err: lori.SetTimerError => None
end
end
fun ref on_response_complete() =>
match _timer
| let t: lori.TimerToken =>
_http.cancel_timer(t)
_timer = None
end
_http.close()
fun ref on_timer(token: lori.TimerToken) =>
_out.print("Response timed out")
_http.close()[0.1.3] - 2026-03-24
Added
- Expose one-shot timer API (PR #42)
0.1.2
Update ponylang/ssl to 2.0.1
Updates the ponylang/ssl dependency to 2.0.1 to pick up a bug fix.
Add connection timeout support
You can now set a connection timeout that bounds how long the TCP (and TLS) handshake phase is allowed to take. If the timeout fires before the connection is ready, on_connection_failure is called with the new ConnectionFailedTimeout reason.
Configure it through ClientConnectionConfig:
let ct = match lori.MakeConnectionTimeout(5_000)
| let t: lori.ConnectionTimeout => t
end
ClientConnectionConfig(where connection_timeout' = ct)The default is None (no timeout), which preserves existing behavior.
If you have an exhaustive match on ConnectionFailureReason, you'll need to add a case for ConnectionFailedTimeout:
// Before
match \exhaustive\ reason
| ConnectionFailedDNS => "DNS failed"
| ConnectionFailedTCP => "TCP failed"
| ConnectionFailedSSL => "SSL failed"
end
// After
match \exhaustive\ reason
| ConnectionFailedDNS => "DNS failed"
| ConnectionFailedTCP => "TCP failed"
| ConnectionFailedSSL => "SSL failed"
| ConnectionFailedTimeout => "Connection timed out"
endFix SSL connection idle timeout issues
Idle timeouts didn't fire reliably on SSL connections. This could cause HTTPS connections to stay open longer than expected when using idle_timeout in ClientConnectionConfig.
Fix connection resource leak on early close
Closing a connection while it was still being established could leak internal connection resources.
[0.1.2] - 2026-03-22
Fixed
- Fix SSL connection idle timeout issues (PR #39)
- Fix connection resource leak on early close (PR #39)
Added
- Add connection timeout support (PR #39)
Changed
- Update ponylang/ssl to 2.0.1 (PR #38)
0.1.1
Fix dispose() hanging when peer FIN is missed
HTTPClientConnectionActor.dispose() could hang indefinitely on POSIX systems when the peer's FIN notification was missed in a narrow timing window. The connection would get stuck in CLOSE_WAIT, preventing the Pony runtime from exiting. Disposal now performs an unconditional hard close, matching what callers expect: immediate teardown without waiting for a protocol exchange.
[0.1.1] - 2026-03-15
Fixed
- Fix dispose() hanging when peer FIN is missed (PR #33)
0.1.0
[0.1.0] - 2026-03-02
Added
- Initial version