@@ -623,12 +623,105 @@ namespace common {
623623 * LockfreeBatchMPMCRingQueue, or LockfreeSPSCRingQueue, with their own template
624624 * parameters.
625625 */
626+ // Shared send-side backoff logic. Members (send_sem, send_waiters,
627+ // send_pending) are declared in each channel class; only the logic is shared.
628+ template <typename T>
629+ struct SendBackoff {
630+ static void notify_senders (photon::semaphore& send_sem,
631+ std::atomic<uint64_t >& send_waiters,
632+ std::atomic<uint64_t >& send_pending) {
633+ std::atomic_thread_fence (std::memory_order_seq_cst);
634+ auto cur_waiters = send_waiters.load (std::memory_order_seq_cst);
635+ if (cur_waiters == 0 ) return ;
636+ auto sp = send_pending.load (std::memory_order_acquire);
637+ for (;;) {
638+ if (sp >= cur_waiters) {
639+ auto fresh = send_waiters.load (std::memory_order_relaxed);
640+ if (fresh <= cur_waiters) return ;
641+ cur_waiters = fresh;
642+ continue ;
643+ }
644+ if (send_pending.compare_exchange_weak (sp, sp + 1 ,
645+ std::memory_order_acq_rel,
646+ std::memory_order_acquire)) {
647+ send_sem.signal (1 );
648+ return ;
649+ }
650+ }
651+ }
652+
653+ template <typename Pause = ThreadPause, typename PushFn,
654+ typename std::enable_if<std::is_same<Pause, PhotonPause>::value, int >::type = 0 >
655+ static void push_backoff (const T& x, PushFn push_fn, uint64_t yield_turn, uint64_t yield_usec,
656+ photon::semaphore& send_sem,
657+ std::atomic<uint64_t >& send_waiters,
658+ std::atomic<uint64_t >& send_pending) {
659+ if (!push_fn (x)) {
660+ send_waiters.fetch_add (1 , std::memory_order_seq_cst);
661+ DEFER (send_waiters.fetch_sub (1 , std::memory_order_seq_cst));
662+ Timeout yield_timeout (yield_usec);
663+ uint64_t yt = yield_turn;
664+ while (!push_fn (x)) {
665+ if (yt > 0 && !yield_timeout.expired ()) {
666+ yt--;
667+ photon::thread_yield ();
668+ } else {
669+ // wait for 100ms
670+ int r = send_sem.wait (1 , 100UL * 1000 );
671+ if (r == 0 )
672+ send_pending.fetch_sub (1 , std::memory_order_acq_rel);
673+ yt = yield_turn;
674+ yield_timeout.timeout (yield_usec);
675+ }
676+ }
677+ }
678+ }
679+
680+ template <typename Pause = ThreadPause, typename PushFn,
681+ typename std::enable_if<std::is_same<Pause, ThreadPause>::value, int >::type = 0 >
682+ static void push_backoff (const T& x, PushFn push_fn, uint64_t yield_turn, uint64_t yield_usec,
683+ photon::semaphore&,
684+ std::atomic<uint64_t >&,
685+ std::atomic<uint64_t >&) {
686+ if (!push_fn (x)) {
687+ uint64_t yt = yield_turn;
688+ auto deadline = std::chrono::steady_clock::now () +
689+ std::chrono::microseconds (yield_usec);
690+ while (!push_fn (x)) {
691+ if (yt > 0 &&
692+ std::chrono::steady_clock::now () < deadline) {
693+ yt--;
694+ std::this_thread::yield ();
695+ } else {
696+ std::this_thread::sleep_for (
697+ std::chrono::microseconds (yield_usec));
698+ yt = yield_turn;
699+ deadline = std::chrono::steady_clock::now () +
700+ std::chrono::microseconds (yield_usec);
701+ }
702+ }
703+ }
704+ }
705+
706+ template <typename Pause = ThreadPause, typename PushFn,
707+ typename std::enable_if<!std::is_same<Pause, PhotonPause>::value &&
708+ !std::is_same<Pause, ThreadPause>::value, int >::type = 0 >
709+ static void push_backoff (const T& x, PushFn push_fn, uint64_t , uint64_t ,
710+ photon::semaphore&, std::atomic<uint64_t >&,
711+ std::atomic<uint64_t >&) {
712+ while (!push_fn (x)) Pause::pause ();
713+ }
714+ };
715+
626716template <typename QueueType>
627717class RingChannel : public QueueType {
628718protected:
629719 photon::semaphore queue_sem;
630720 std::atomic<uint64_t > idler{0 }; // # consumers in idle/wait
631721 std::atomic<uint64_t > pending{0 }; // mirror of queue_sem.m_count
722+ photon::semaphore send_sem;
723+ std::atomic<uint64_t > send_waiters{0 };
724+ std::atomic<uint64_t > send_pending{0 };
632725 uint64_t default_yield_turn = 1024 ;
633726 uint64_t default_yield_usec = 1024 ;
634727
@@ -649,9 +742,9 @@ class RingChannel : public QueueType {
649742
650743 template <typename Pause = ThreadPause>
651744 void send (const T& x) {
652- while (! push (x)) {
653- Pause::pause ();
654- }
745+ SendBackoff<T>:: template push_backoff<Pause>(x, [ this ]( const T& v) { return push (v); },
746+ default_yield_turn, default_yield_usec,
747+ send_sem, send_waiters, send_pending);
655748 // Dekker barrier: ensure the prior push (mark.store release) is
656749 // ordered before the following idler load, paired with the seq_cst
657750 // RMW on `idler` in recv(). This guarantees that we cannot
@@ -683,7 +776,10 @@ class RingChannel : public QueueType {
683776 }
684777 T recv (uint64_t max_yield_turn, uint64_t max_yield_usec) {
685778 T x;
686- if (pop (x)) return x;
779+ if (pop (x)) {
780+ SendBackoff<T>::notify_senders (send_sem, send_waiters, send_pending);
781+ return x;
782+ }
687783 // yield once if failed, so photon::now will be updated
688784 photon::thread_yield ();
689785 // seq_cst on idler is the other half of the Dekker barrier (see send).
@@ -708,6 +804,7 @@ class RingChannel : public QueueType {
708804 yield_timeout.timeout (max_yield_usec);
709805 }
710806 }
807+ SendBackoff<T>::notify_senders (send_sem, send_waiters, send_pending);
711808 return x;
712809 }
713810 T recv () { return recv (default_yield_turn, default_yield_usec); }
@@ -738,6 +835,9 @@ class FlexRingChannel {
738835 photon::semaphore queue_sem;
739836 std::atomic<uint64_t > idler{0 }; // # consumers in idle/wait
740837 std::atomic<uint64_t > pending{0 }; // mirror of queue_sem.m_count
838+ photon::semaphore send_sem;
839+ std::atomic<uint64_t > send_waiters{0 };
840+ std::atomic<uint64_t > send_pending{0 };
741841 uint64_t default_yield_turn = 1024 ;
742842 uint64_t default_yield_usec = 1024 ;
743843
@@ -769,7 +869,9 @@ class FlexRingChannel {
769869
770870 template <typename Pause = ThreadPause>
771871 void send (const T& x) {
772- queue->template send <Pause>(x);
872+ SendBackoff<T>::template push_backoff<Pause>(x, [this ](const T& v) { return queue->push (v); },
873+ default_yield_turn, default_yield_usec,
874+ send_sem, send_waiters, send_pending);
773875 // Dekker barrier: ensure the prior push is ordered before the
774876 // following idler load, paired with the seq_cst RMW on `idler`
775877 // in recv().
@@ -798,7 +900,10 @@ class FlexRingChannel {
798900
799901 T recv (uint64_t max_yield_turn, uint64_t max_yield_usec) {
800902 T x;
801- if (queue->pop (x)) return x;
903+ if (queue->pop (x)) {
904+ SendBackoff<T>::notify_senders (send_sem, send_waiters, send_pending);
905+ return x;
906+ }
802907 // yield once if failed, so photon::now will be updated
803908 photon::thread_yield ();
804909 // seq_cst on idler is the other half of the Dekker barrier (see send).
@@ -823,6 +928,7 @@ class FlexRingChannel {
823928 yield_timeout.timeout (max_yield_usec);
824929 }
825930 }
931+ SendBackoff<T>::notify_senders (send_sem, send_waiters, send_pending);
826932 return x;
827933 }
828934
0 commit comments