@@ -363,6 +363,18 @@ win_socket_impl_internal(win_sockets& svc) noexcept
363363win_socket_impl_internal::
364364~win_socket_impl_internal ()
365365{
366+ // Destroy any active initiator coroutines
367+ if (read_initiator_handle_)
368+ read_initiator_handle_.destroy ();
369+ if (write_initiator_handle_)
370+ write_initiator_handle_.destroy ();
371+
372+ // Free cached frame storage (operator delete in promise_type is no-op)
373+ if (read_initiator_frame_)
374+ ::operator delete (read_initiator_frame_);
375+ if (write_initiator_frame_)
376+ ::operator delete (write_initiator_frame_);
377+
366378 svc_.unregister_impl (*this );
367379}
368380
@@ -469,46 +481,31 @@ connect(
469481 }
470482}
471483
472- std::coroutine_handle<>
473- win_socket_impl_internal::
474- read_some (
475- capy::coro h,
476- capy::executor_ref d,
477- io_buffer_param param,
478- std::stop_token token,
479- std::error_code* ec,
480- std::size_t * bytes_out)
481- {
482- // Keep internal alive during I/O
483- rd_.internal_ptr = shared_from_this ();
484+ // ------------------------------------------------------------------------------
485+ // Initiator coroutines - receive control via symmetric transfer after caller
486+ // suspends, then initiate the actual I/O.
484487
485- auto & op = rd_;
486- op.reset ();
487- op.h = h;
488- op.d = d;
489- op.ec_out = ec;
490- op.bytes_out = bytes_out;
491- op.start (token);
488+ read_initiator
489+ make_read_initiator (void *& cached, win_socket_impl_internal* impl)
490+ {
491+ impl->do_read_io ();
492+ co_return ;
493+ }
492494
493- capy::mutable_buffer bufs[read_op::max_buffers];
494- op.wsabuf_count = static_cast <DWORD >(
495- param.copy_to (bufs, read_op::max_buffers));
495+ write_initiator
496+ make_write_initiator (void *& cached, win_socket_impl_internal* impl)
497+ {
498+ impl->do_write_io ();
499+ co_return ;
500+ }
496501
497- // Handle empty buffer: complete with 0 bytes via post for consistency
498- if (op.wsabuf_count == 0 )
499- {
500- op.bytes_transferred = 0 ;
501- op.dwError = 0 ;
502- op.empty_buffer = true ;
503- svc_.post (&op);
504- return std::noop_coroutine ();
505- }
502+ // ------------------------------------------------------------------------------
506503
507- for ( DWORD i = 0 ; i < op. wsabuf_count ; ++i)
508- {
509- op. wsabufs [i]. buf = static_cast < char *>(bufs[i]. data ());
510- op. wsabufs [i]. len = static_cast < ULONG >(bufs[i]. size ());
511- }
504+ void
505+ win_socket_impl_internal::
506+ do_read_io ()
507+ {
508+ auto & op = rd_;
512509
513510 op.flags = 0 ;
514511
@@ -532,7 +529,7 @@ read_some(
532529 svc_.work_finished ();
533530 op.dwError = err;
534531 op.complete_immediate ();
535- return std::noop_coroutine () ;
532+ return ;
536533 }
537534 }
538535 else
@@ -555,7 +552,109 @@ read_some(
555552 svc_.post (&op);
556553 }
557554 }
558- return std::noop_coroutine ();
555+ }
556+
557+ void
558+ win_socket_impl_internal::
559+ do_write_io ()
560+ {
561+ auto & op = wr_;
562+
563+ svc_.work_started ();
564+
565+ int result = ::WSASend (
566+ socket_,
567+ op.wsabufs ,
568+ op.wsabuf_count ,
569+ nullptr ,
570+ 0 ,
571+ &op,
572+ nullptr );
573+
574+ if (result == SOCKET_ERROR )
575+ {
576+ DWORD err = ::WSAGetLastError ();
577+ if (err != WSA_IO_PENDING )
578+ {
579+ // Immediate error - must use post(). See do_read_io for explanation.
580+ svc_.work_finished ();
581+ op.dwError = err;
582+ svc_.post (&op);
583+ return ;
584+ }
585+ }
586+ else
587+ {
588+ // Synchronous completion - use CAS to race with IOCP.
589+ // See do_read_io for detailed explanation.
590+ //
591+ // CRITICAL: Must call work_finished() ONLY if we win the CAS, and must
592+ // not access op after CAS fails. If IOCP wins, it processes the op
593+ // (which may destroy it), so any access to op is use-after-free.
594+ // The IOCP handler calls work_finished() via its work_guard.
595+ if (::InterlockedCompareExchange (&op.ready_ , 1 , 0 ) == 0 )
596+ {
597+ svc_.work_finished ();
598+ op.bytes_transferred = static_cast <DWORD >(op.InternalHigh );
599+ op.dwError = 0 ;
600+ svc_.post (&op);
601+ }
602+ }
603+ }
604+
605+ // ------------------------------------------------------------------------------
606+
607+ std::coroutine_handle<>
608+ win_socket_impl_internal::
609+ read_some (
610+ capy::coro h,
611+ capy::executor_ref d,
612+ io_buffer_param param,
613+ std::stop_token token,
614+ std::error_code* ec,
615+ std::size_t * bytes_out)
616+ {
617+ // Keep internal alive during I/O
618+ rd_.internal_ptr = shared_from_this ();
619+
620+ auto & op = rd_;
621+ op.reset ();
622+ op.h = h;
623+ op.d = d;
624+ op.ec_out = ec;
625+ op.bytes_out = bytes_out;
626+ op.start (token);
627+
628+ // Prepare buffers (must happen before initiator runs)
629+ capy::mutable_buffer bufs[read_op::max_buffers];
630+ op.wsabuf_count = static_cast <DWORD >(
631+ param.copy_to (bufs, read_op::max_buffers));
632+
633+ // Handle empty buffer: complete with 0 bytes via post for consistency
634+ if (op.wsabuf_count == 0 )
635+ {
636+ op.bytes_transferred = 0 ;
637+ op.dwError = 0 ;
638+ op.empty_buffer = true ;
639+ svc_.post (&op);
640+ return std::noop_coroutine ();
641+ }
642+
643+ for (DWORD i = 0 ; i < op.wsabuf_count ; ++i)
644+ {
645+ op.wsabufs [i].buf = static_cast <char *>(bufs[i].data ());
646+ op.wsabufs [i].len = static_cast <ULONG >(bufs[i].size ());
647+ }
648+
649+ // Destroy previous initiator if any, construct new one into cached frame
650+ if (read_initiator_handle_)
651+ read_initiator_handle_.destroy ();
652+
653+ auto initiator = make_read_initiator (read_initiator_frame_, this );
654+ read_initiator_handle_ = initiator.h ;
655+
656+ // Symmetric transfer to initiator - I/O starts after caller is suspended
657+ return initiator.h ;
559658}
560659
561660std::coroutine_handle<>
@@ -579,6 +678,7 @@ write_some(
579678 op.bytes_out = bytes_out;
580679 op.start (token);
581680
681+ // Prepare buffers (must happen before initiator runs)
582682 capy::mutable_buffer bufs[write_op::max_buffers];
583683 op.wsabuf_count = static_cast <DWORD >(
584684 param.copy_to (bufs, write_op::max_buffers));
@@ -598,47 +698,15 @@ write_some(
598698 op.wsabufs [i].len = static_cast <ULONG >(bufs[i].size ());
599699 }
600700
601- svc_.work_started ();
701+ // Destroy previous initiator if any, construct new one into cached frame
702+ if (write_initiator_handle_)
703+ write_initiator_handle_.destroy ();
602704
603- int result = ::WSASend (
604- socket_,
605- op.wsabufs ,
606- op.wsabuf_count ,
607- nullptr ,
608- 0 ,
609- &op,
610- nullptr );
705+ auto initiator = make_write_initiator (write_initiator_frame_, this );
706+ write_initiator_handle_ = initiator.h ;
611707
612- if (result == SOCKET_ERROR )
613- {
614- DWORD err = ::WSAGetLastError ();
615- if (err != WSA_IO_PENDING )
616- {
617- // Immediate error - must use post(). See read_some for explanation.
618- svc_.work_finished ();
619- op.dwError = err;
620- svc_.post (&op);
621- return std::noop_coroutine ();
622- }
623- }
624- else
625- {
626- // Synchronous completion - use CAS to race with IOCP.
627- // See read_some for detailed explanation.
628- //
629- // CRITICAL: Must call work_finished() ONLY if we win the CAS, and must
630- // not access op after CAS fails. If IOCP wins, it processes the op
631- // (which may destroy it), so any access to op is use-after-free.
632- // The IOCP handler calls work_finished() via its work_guard.
633- if (::InterlockedCompareExchange (&op.ready_ , 1 , 0 ) == 0 )
634- {
635- svc_.work_finished ();
636- op.bytes_transferred = static_cast <DWORD >(op.InternalHigh );
637- op.dwError = 0 ;
638- svc_.post (&op);
639- }
640- }
641- return std::noop_coroutine ();
708+ // Symmetric transfer to initiator - I/O starts after caller is suspended
709+ return initiator.h ;
642710}
643711
644712void
0 commit comments