Implementation plan: SSL as first-class TCPConnection concern #158
Replies: 4 comments
-
|
Decision point in Step 2: Add pre-connection send queueing to TCPConnection. we should return a send error. |
Beta Was this translation helpful? Give feedback.
-
Revised plan: No pre-connection send queueingAfter review, the plan has been revised. The pre-connection send queue (original Step 2) is removed, and several design decisions have been resolved. This comment supersedes the send system portions of the original plan body; the SSL integration steps are mostly unchanged. Design principle: TCPConnection does not queue application dataTCPConnection does not queue data on behalf of the application. The only data TCPConnection holds in its If the application wants to send data before the connection is established, during backpressure, or in any other situation where This means:
Resolved design decisions
Client SSL constructor error handling: The
Post-close send behavior: SSL-specific callbacks: Revised implementation stepsStep 1: Add File: Add to both fun ref _on_send_failed(token: SendToken) => NoneAdditive and backwards compatible. Step 2: Add File: be _notify_send_failed(token: SendToken) =>
_connection()._fire_on_send_failed(token)File: fun ref _fire_on_send_failed(token: SendToken) =>
match _lifecycle_event_receiver
| let s: EitherLifecycleEventReceiver ref =>
s._on_send_failed(token)
| None =>
_Unreachable()
endStep 3: Modify File: Before clearing match _pending_token
| let t: SendToken =>
_pending_token = None
match _enclosing
| let e: TCPConnectionActor ref =>
e._notify_send_failed(t)
| None =>
_Unreachable()
end
end
_pending.clear()Note: The Windows IOCP Step 4: Add SSL constructors to File: New fields: var _ssl: (SSL ref | None) = None
var _ssl_ready: Bool = false
var _ssl_failed: Bool = false
Both constructors should have docstrings. Step 5: Add internal SSL logic to File: Move logic from On connection established:
Incoming data (in
Outgoing data (in
On close (in
Expect handling:
Step 6: Remove DataInterceptor infrastructure Files:
Step 7: Remove SSL interceptor classes and File: File:
Step 8: Update examples Replace interceptor usage with Step 9: Update tests Build and run tests with
Step 10: Update CLAUDE.md Update to reflect: new constructors, no-queueing design principle, Revised phasing
What changed from the original plan
|
Beta Was this translation helpful? Give feedback.
-
Review findings and resolved decisionsReview of the revised plan (comment above) surfaced the following. Design decisions have been resolved through discussion; editorial items are noted for implementation. Design decisions resolved
New Editorial corrections for implementation
|
Beta Was this translation helpful? Give feedback.
-
|
The revised plan was fully implemented across two PRs:
Send queueing (Steps 2 and 4 of the original plan) was dropped during implementation — |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Context
This is a concrete implementation plan for Direction A from Discussion #157 (SSL as a first-class TCPConnection concern). It builds on the design decisions captured in that discussion.
Design: #157
Summary of design decisions
client,server,ssl_client,ssl_serverSSLContextas dependency injection point;TCPConnectioncreatesSSLsession internallyponylang/ssllibrary_on_sentor_on_send_failed)_on_connection_failure(); no SSL-specific callbacksNetSSLLifecycleEventReceiver(on_alpn_negotiated,on_ssl_auth_failed,on_ssl_error) intentionally dropped — these can be added back later in an additive fashion if neededDataInterceptortrait and related infrastructure removedSendErrorNotConnectedandSendErrorNotReadyremoved (sends are queued instead)Implementation steps
Step 1: Add
_on_send_failedto lifecycle receiver traitsFile:
lifecycle_event_receiver.ponyAdd to both
ServerLifecycleEventReceiverandClientLifecycleEventReceiver:This is additive and backwards compatible — existing implementors inherit the default.
Step 2: Add pre-connection send queueing to
TCPConnectionFile:
tcp_connection.ponyAdd a queue for sends that arrive before the connection is established:
(ByteSeq, SendToken)pairs for pre-connection sendssend(): when not connected (or SSL handshake in progress), queue the data and return aSendTokeninstead of returningSendErrorNotConnectedorSendErrorNotReady_on_send_failed(token)for each queued item via the_notify_send_failedbehavior onTCPConnectionActorDecision point: What should
send()do after_on_closedor_on_connection_failurehas been delivered? Options: (a) return a new error type likeSendErrorClosed, (b) accept and immediately schedule_on_send_failed, or (c) silently drop. This needs to be resolved — leaning toward (a) for explicitness since the user already knows the connection is gone.Also modify the existing pending write buffer behavior: when the connection drops with data in the OS write buffer (
_pending), fire_on_send_failedfor the associatedSendTokenif one exists.Step 3: Add
_notify_send_failedbehavior toTCPConnectionActorFile:
tcp_connection_actor.ponyAdd a new behavior parallel to
_notify_sent:And add
_fire_on_send_failedtoTCPConnectionthat delivers to the lifecycle receiver.This ensures
_on_send_failedis always delivered in a subsequent behavior turn, never synchronously duringsend()orclose(), matching the pattern established by_on_sent.Step 4: Remove
SendErrorNotConnectedandSendErrorNotReadyFile:
send_token.ponyRemove the
SendErrorNotConnectedandSendErrorNotReadyprimitives.SendErrorNotWriteableremains for backpressure. Update theSendErrortype alias accordingly. If a "send after closed" error is added (Step 2 decision point), add that here.Step 5: Add SSL constructors to
TCPConnectionFile:
tcp_connection.ponyAdd new fields for SSL state:
_ssl: (SSL ref | None)— the per-connection SSL session_ssl_ready: Bool— whether handshake is completeAdd two new constructors:
Internally calls
ssl_ctx.client(host)to create theSSL isosession. Otherwise identical setup toclient().Internally calls
ssl_ctx.server()to create theSSL isosession. Otherwise identical setup toserver().Both constructors must handle the case where
ssl_ctx.client()orssl_ctx.server()raises an error (partial function) — the connection should fail gracefully, eventually delivering_on_connection_failureor equivalent.Step 6: Add internal SSL logic to
TCPConnectionFile:
tcp_connection.ponyMove the logic currently in
SSLClientInterceptorandSSLServerInterceptorintoTCPConnectionprivate methods. Key additions:On connection established (in
_complete_client_initialization/_complete_server_initialization):_sslis notNone, initiate the handshake: flush initial SSL data (ClientHello for clients) via_ssl.can_send()/_ssl.send()→_send_final()_on_connected/_on_startedyet — wait for handshakeIncoming data (in
_deliver_received):_sslis notNone: feed data to_ssl.receive(), then call a new_ssl_poll()method_ssl_poll()checks_ssl.state():SSLReadyand not yet_ssl_ready: set_ssl_ready = true, deliver_on_connected/_on_started, flush pre-connection send queueSSLAuthFail/SSLError: callhard_close(), which will trigger_on_connection_failure(for clients pre-ready) or_on_closed(for servers or post-ready)_ssl.read()in a loop, deliver each chunk to the lifecycle receiver's_on_received_ssl.can_send()/_ssl.send()→_send_final()Outgoing data (in
send()):_sslis notNoneand_ssl_ready: encrypt via_ssl.write(), then send encrypted data via_ssl.can_send()/_ssl.send()→_send_final()_sslis notNoneand not_ssl_ready: queue in pre-connection queue (Step 2)On close (in
hard_close()):_sslis notNone: call_ssl.dispose()Expect handling:
_sslis notNone: store the application's expect value internally, but tell the TCP read layer to use 0 (read all available). Apply the stored expect value when chunking decrypted data from_ssl.read().Step 7: Remove DataInterceptor infrastructure
Files:
tcp_connection.pony,data_interceptor.ponydata_interceptor.ponyentirely (removesDataInterceptor,WireSender,IncomingDataReceiver,InterceptorControl)tcp_connection.pony:_WireSenderAdapter,_IncomingDataReceiverAdapter,_InterceptorControlAdapterinterceptorparameter fromclient()andserver()constructors_interceptor,_interceptor_ready,_wire_sender,_incoming_receiver,_interceptor_control_interceptor_signal_ready()methodsend(),_deliver_received(),is_writeable(),hard_close(),expect(), and initialization methodsStep 8: Remove SSL interceptor classes
File:
net_ssl_connection.ponyDelete the file entirely (removes
NetSSLLifecycleEventReceiver,SSLClientInterceptor,SSLServerInterceptor). Their logic has been absorbed intoTCPConnectionin Step 6.Step 9: Update examples
examples/net-ssl-echo-server/: ReplaceSSLServerInterceptorusage withTCPConnection.ssl_server()constructor. Remove interceptor creation. The echo server actor's structure stays the same — it still implementsTCPConnectionActor & ServerLifecycleEventReceiver.examples/net-ssl-infinite-ping-pong/: ReplaceSSLClientInterceptor/SSLServerInterceptorusage withTCPConnection.ssl_client()/TCPConnection.ssl_server()constructors. Remove interceptor creation.examples/echo-server/andexamples/infinite-ping-pong/: No changes (no SSL).Step 10: Update tests
_test.pony:_TestSSLPingPongand related test actors to usessl_client/ssl_serverconstructors instead of interceptors_on_send_failedfires for each queued send_TestSendAfterClose— it currently checks forSendErrorNotConnected, which is being removed. Update to match the new post-close send behavior (see Step 2 decision point)Step 11: Update CLAUDE.md
Update the project CLAUDE.md to reflect:
_on_send_failedcallbackOrdering and dependencies
Steps 1-4 (send system changes) can be done first as a standalone improvement — they don't require SSL changes. Steps 5-6 (SSL internals) depend on step 2 (pre-connection queue). Steps 7-8 (removal) depend on steps 5-6 and 9-10 (examples/tests updated to new API). Step 11 is last.
A possible phased approach:
SendErrorNotReady(Step 4) requires changing the interceptor-not-ready code path insend()to queue instead of returning an error. Phase 1 must handle both the!is_open()and!_interceptor_readycases with queueing.Or it can be done as a single branch if preferred.
Beta Was this translation helpful? Give feedback.
All reactions