11#pragma once
22
33#include < cassert>
4+ #include < limits>
5+ #include < optional>
46#include < random>
57
68#include " scheduler.h"
@@ -25,23 +27,21 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
2527 PrepareForDepth (current_depth, avg_k);
2628 }
2729
28- // If there aren't any non returned tasks and the amount of finished tasks
29- // is equal to the max_tasks the finished task will be returned
30- TaskWithMetaData Next () override {
31- return this ->NextVerifiedFor (NextThreadId ());
32- }
33-
34- size_t NextThreadId () override {
30+ std::optional<size_t > NextThreadId () override {
3531 auto & threads = this ->threads ;
36- int max = std::numeric_limits<int >::min ();
32+ size_t max = std::numeric_limits<size_t >::min ();
3733 size_t index_of_max = 0 ;
3834 // Have to ignore waiting threads, so can't do it faster than O(n)
3935 for (size_t i = 0 ; i < threads.size (); ++i) {
4036 // Ignore waiting tasks
41- // debug(stderr, "prior: %d, number %d\n", priorities[i], i);
37+ debug (stderr, " prior: %d, number %d\n " , priorities[i], i);
4238 if (!threads[i].empty () && threads[i].back ()->IsBlocked ()) {
43- // debug(stderr, "blocked on %p val %d\n",
44- // threads[i].back()->fstate.addr, threads[i].back()->fstate.value);
39+ // NOTE(kmitkin): if we don't get deadlock here, then we able to make a
40+ // progress,
41+ // so we have to check spinlock condition again
42+ threads[i].back ()->SetSpinLocked (false );
43+ debug (stderr, " blocked on %p val %d\n " , threads[i].back ()->fstate .addr ,
44+ threads[i].back ()->fstate .value );
4545 // dual waiting if request finished, but follow up isn't
4646 // skip dual tasks that already have finished the request
4747 // section(follow-up will be executed in another task, so we can't
@@ -55,10 +55,11 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
5555 }
5656 }
5757
58- if (round_robin_stage > 0 ) {
58+ if (round_robin_stage > 0 ) [[unlikely]] {
5959 for (size_t attempt = 0 ; attempt < threads.size (); ++attempt) {
6060 auto i = (++last_chosen) % threads.size ();
6161 if (!threads[i].empty () && threads[i].back ()->IsBlocked ()) {
62+ threads[i].back ()->SetSpinLocked (false );
6263 continue ;
6364 }
6465 index_of_max = i;
@@ -72,7 +73,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
7273 }
7374
7475 // TODO: Choose wiser constant
75- if (count_chosen_same == 1000 && index_of_max == last_chosen) {
76+ if (count_chosen_same == 1000 && index_of_max == last_chosen) [[unlikely]] {
7677 round_robin_stage = 5 ;
7778 round_robin_start = index_of_max;
7879 }
@@ -83,8 +84,9 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
8384 count_chosen_same = 1 ;
8485 }
8586
86- assert (max != std::numeric_limits<int >::min () &&
87- " all threads are empty or blocked" );
87+ if (max == std::numeric_limits<size_t >::min ()) [[unlikely]] {
88+ return std::nullopt ;
89+ }
8890
8991 // Check whether the priority change is required
9092 current_schedule_length++;
@@ -94,25 +96,26 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
9496 }
9597 }
9698
97- // debug(stderr, "Chosen thread: %d, cnt_count: %d\n", index_of_max,
98- // count_chosen_same);
99+ debug (stderr, " Chosen thread: %d, cnt_count: %d\n " , index_of_max,
100+ count_chosen_same);
99101 last_chosen = index_of_max;
100102 return index_of_max;
101103 }
102104
103105 // NOTE: `Next` version use heuristics for livelock avoiding, but not there
104106 // refactor later to avoid copy-paste
105- TaskWithMetaData NextSchedule () override {
107+ std::optional< TaskWithMetaData> NextSchedule () override {
106108 auto & round_schedule = this ->round_schedule ;
107109 auto & threads = this ->threads ;
108- int max = std::numeric_limits<int >::min ();
110+ size_t max = std::numeric_limits<size_t >::min ();
109111 size_t index_of_max = 0 ;
110112 // Have to ignore waiting threads, so can't do it faster than O(n)
111113 for (size_t i = 0 ; i < threads.size (); ++i) {
112114 int task_index = this ->GetNextTaskInThread (i);
113115 // Ignore waiting tasks
114116 if (task_index == threads[i].size () ||
115117 threads[i][task_index]->IsBlocked ()) {
118+ threads[i][task_index]->SetSpinLocked (false );
116119 // dual waiting if request finished, but follow up isn't
117120 // skip dual tasks that already have finished the request
118121 // section(follow-up will be executed in another task, so we can't
@@ -126,7 +129,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
126129 }
127130 }
128131
129- if (round_robin_stage > 0 ) {
132+ if (round_robin_stage > 0 ) [[unlikely]] {
130133 for (size_t attempt = 0 ; attempt < threads.size (); ++attempt) {
131134 auto i = (++last_chosen) % threads.size ();
132135 int task_index = this ->GetNextTaskInThread (i);
@@ -145,7 +148,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
145148 }
146149
147150 // TODO: Choose wiser constant
148- if (count_chosen_same == 1000 && index_of_max == last_chosen) {
151+ if (count_chosen_same == 1000 && index_of_max == last_chosen) [[unlikely]] {
149152 round_robin_stage = 5 ;
150153 round_robin_start = index_of_max;
151154 }
@@ -155,6 +158,11 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
155158 } else {
156159 count_chosen_same = 1 ;
157160 }
161+
162+ if (max == std::numeric_limits<size_t >::min ()) {
163+ return std::nullopt ;
164+ }
165+
158166 last_chosen = index_of_max;
159167 // Picked thread is `index_of_max`
160168 int next_task_index = this ->GetNextTaskInThread (index_of_max);
0 commit comments