1919
2020typedef struct {
2121 int fd ;
22+ int connecting ;
2223} zi_tcp_stream ;
2324
2425static void set_nonblocking_best_effort (int fd ) {
@@ -36,6 +37,43 @@ static int tcp_get_fd(void *ctx, int *out_fd) {
3637 return 1 ;
3738}
3839
40+ static int32_t map_errno_to_zi (int e );
41+
42+ static int32_t tcp_ensure_connected (zi_tcp_stream * s ) {
43+ if (!s ) return ZI_E_INTERNAL ;
44+ if (!s -> connecting ) return 0 ;
45+ if (s -> fd < 0 ) return ZI_E_CLOSED ;
46+
47+ int so_err = 0 ;
48+ socklen_t len = (socklen_t )sizeof (so_err );
49+ if (getsockopt (s -> fd , SOL_SOCKET , SO_ERROR , & so_err , & len ) != 0 ) {
50+ return map_errno_to_zi (errno );
51+ }
52+ if (so_err == 0 ) {
53+ // Some platforms may report SO_ERROR=0 before the connection is fully established.
54+ // Confirm connectivity via getpeername.
55+ struct sockaddr_storage ss ;
56+ socklen_t slen = (socklen_t )sizeof (ss );
57+ if (getpeername (s -> fd , (struct sockaddr * )& ss , & slen ) == 0 ) {
58+ s -> connecting = 0 ;
59+ return 0 ;
60+ }
61+ if (errno == ENOTCONN ) return ZI_E_AGAIN ;
62+ return map_errno_to_zi (errno );
63+ }
64+
65+ // Still in progress.
66+ if (so_err == EINPROGRESS
67+ #if defined(EWOULDBLOCK ) && (EWOULDBLOCK != EINPROGRESS )
68+ || so_err == EWOULDBLOCK
69+ #endif
70+ ) {
71+ return ZI_E_AGAIN ;
72+ }
73+
74+ return map_errno_to_zi (so_err );
75+ }
76+
3977static int32_t map_errno_to_zi (int e ) {
4078 switch (e ) {
4179 case EAGAIN :
@@ -64,6 +102,9 @@ static int32_t tcp_read(void *ctx, zi_ptr_t dst_ptr, zi_size32_t cap) {
64102 if (!s ) return ZI_E_INTERNAL ;
65103 if (cap == 0 ) return 0 ;
66104
105+ int32_t cr = tcp_ensure_connected (s );
106+ if (cr != 0 ) return cr ;
107+
67108 const zi_mem_v1 * mem = zi_runtime25_mem ();
68109 if (!mem || !mem -> map_rw ) return ZI_E_NOSYS ;
69110 if (dst_ptr == 0 ) return ZI_E_BOUNDS ;
@@ -81,6 +122,9 @@ static int32_t tcp_write(void *ctx, zi_ptr_t src_ptr, zi_size32_t len) {
81122 if (!s ) return ZI_E_INTERNAL ;
82123 if (len == 0 ) return 0 ;
83124
125+ int32_t cr = tcp_ensure_connected (s );
126+ if (cr != 0 ) return cr ;
127+
84128 const zi_mem_v1 * mem = zi_runtime25_mem ();
85129 if (!mem || !mem -> map_ro ) return ZI_E_NOSYS ;
86130 if (src_ptr == 0 ) return ZI_E_BOUNDS ;
@@ -331,38 +375,52 @@ zi_handle_t zi_net_tcp25_open_from_params(zi_ptr_t params_ptr, zi_size32_t param
331375 continue ;
332376 }
333377
378+ // Make connect + subsequent I/O nonblocking so callers can rely on ZI_E_AGAIN.
379+ set_nonblocking_best_effort (fd );
380+
334381#if defined(__APPLE__ ) && defined(SO_NOSIGPIPE )
335382 {
336383 int one = 1 ;
337384 (void )setsockopt (fd , SOL_SOCKET , SO_NOSIGPIPE , & one , (socklen_t )sizeof (one ));
338385 }
339386#endif
340387
341- if (connect (fd , cur -> ai_addr , cur -> ai_addrlen ) == 0 ) {
342- last_zi = 0 ;
388+ for (;;) {
389+ if (connect (fd , cur -> ai_addr , cur -> ai_addrlen ) == 0 ) {
390+ last_zi = 0 ;
391+ break ;
392+ }
393+
394+ if (errno == EINTR ) continue ;
395+ if (errno == EINPROGRESS
396+ #if defined(EWOULDBLOCK ) && (EWOULDBLOCK != EINPROGRESS )
397+ || errno == EWOULDBLOCK
398+ #endif
399+ ) {
400+ last_zi = ZI_E_AGAIN ;
401+ break ;
402+ }
403+
404+ last_zi = map_errno_to_zi (errno );
405+ (void )close (fd );
406+ fd = -1 ;
343407 break ;
344408 }
345409
346- last_zi = map_errno_to_zi (errno );
347- (void )close (fd );
348- fd = -1 ;
410+ if (fd >= 0 ) break ;
349411 }
350412
351413 freeaddrinfo (ai );
352414
353- if (fd < 0 ) {
354- return (zi_handle_t )last_zi ;
355- }
356-
357- // Make subsequent I/O nonblocking so callers can rely on ZI_E_AGAIN.
358- set_nonblocking_best_effort (fd );
415+ if (fd < 0 ) return (zi_handle_t )last_zi ;
359416
360417 zi_tcp_stream * s = (zi_tcp_stream * )calloc (1 , sizeof (* s ));
361418 if (!s ) {
362419 (void )close (fd );
363420 return (zi_handle_t )ZI_E_OOM ;
364421 }
365422 s -> fd = fd ;
423+ s -> connecting = (last_zi == ZI_E_AGAIN ) ? 1 : 0 ;
366424
367425 zi_handle_t h = zi_handle25_alloc_with_poll (& tcp_ops , & tcp_poll_ops , s , ZI_H_READABLE | ZI_H_WRITABLE | ZI_H_ENDABLE );
368426 if (h == 0 ) {
0 commit comments