Skip to content

Commit 38adfdb

Browse files
authored
Synnax 52.9 (#2064)
1 parent 96df3d1 commit 38adfdb

131 files changed

Lines changed: 12239 additions & 313 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

arc/cpp/runtime/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ cc_test(
2727
srcs = ["runtime_test.cpp"],
2828
deps = [
2929
":runtime",
30+
"//arc/cpp/ir/testutil",
3031
"//arc/cpp/runtime/testutil",
3132
"//x/cpp/telem",
3233
"//x/cpp/test",

arc/cpp/runtime/loop/loop.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,14 @@ struct Loop {
249249
/// @brief Block until timer/external event or breaker stops.
250250
/// Must be called from the runtime thread only.
251251
/// @param breaker Controls loop termination; wait() returns when breaker stops.
252+
/// @param max_timeout Upper bound on how long to sleep. When positive, the loop
253+
/// will wake after at most this duration even if no timer or input fires.
254+
/// A value of 0 means no deadline constraint (use the loop's configured timing).
252255
/// @return WakeReason indicating why wait() returned.
253-
virtual WakeReason wait(x::breaker::Breaker &breaker) = 0;
256+
virtual WakeReason wait(
257+
x::breaker::Breaker &breaker,
258+
x::telem::TimeSpan max_timeout = x::telem::TimeSpan(0)
259+
) = 0;
254260

255261
/// @brief Initialize loop resources. Must be called before wait().
256262
/// Applies RT configuration (priority, affinity, memory lock) if configured.

arc/cpp/runtime/loop/loop_darwin.cpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,22 @@ class DarwinLoop final : public Loop {
3939

4040
~DarwinLoop() override { this->close_fds(); }
4141

42-
WakeReason wait(x::breaker::Breaker &breaker) override {
42+
WakeReason wait(
43+
x::breaker::Breaker &breaker,
44+
x::telem::TimeSpan max_timeout = x::telem::TimeSpan(0)
45+
) override {
4346
if (this->kqueue_fd_ == -1) return WakeReason::Shutdown;
4447

4548
switch (this->config_.mode) {
4649
case ExecutionMode::AUTO:
4750
case ExecutionMode::EVENT_DRIVEN:
48-
return this->event_driven_wait();
51+
return this->event_driven_wait(max_timeout);
4952
case ExecutionMode::BUSY_WAIT:
5053
return this->busy_wait(breaker);
5154
case ExecutionMode::HIGH_RATE:
5255
return this->high_rate_wait(breaker);
5356
case ExecutionMode::HYBRID:
54-
return this->hybrid_wait(breaker);
57+
return this->hybrid_wait(breaker, max_timeout);
5558
case ExecutionMode::RT_EVENT:
5659
return this->high_rate_wait(breaker);
5760
}
@@ -195,7 +198,10 @@ class DarwinLoop final : public Loop {
195198
}
196199

197200
/// @brief HYBRID: Spin for configured duration, then block with timeout.
198-
WakeReason hybrid_wait(const x::breaker::Breaker &breaker) const {
201+
WakeReason hybrid_wait(
202+
const x::breaker::Breaker &breaker,
203+
const x::telem::TimeSpan max_timeout
204+
) const {
199205
const auto spin_start = std::chrono::steady_clock::now();
200206
const auto spin_duration = this->config_.spin_duration.chrono();
201207
struct timespec timeout = {0, 0};
@@ -205,18 +211,22 @@ class DarwinLoop final : public Loop {
205211
const int n = kevent(this->kqueue_fd_, nullptr, 0, events, 8, &timeout);
206212
if (n > 0) return this->classify_events(events, n);
207213
}
208-
const auto block_timeout_ns = timing::HYBRID_BLOCK_TIMEOUT.nanoseconds();
209-
timeout.tv_sec = 0;
210-
timeout.tv_nsec = block_timeout_ns;
214+
const auto block_ns = max_timeout.nanoseconds() > 0
215+
? max_timeout.nanoseconds()
216+
: timing::HYBRID_BLOCK_TIMEOUT.nanoseconds();
217+
timeout = ns_to_timespec(block_ns);
211218
const int n = kevent(this->kqueue_fd_, nullptr, 0, events, 8, &timeout);
212219
if (n > 0) return this->classify_events(events, n);
213220
return WakeReason::Timeout;
214221
}
215222

216223
/// @brief EVENT_DRIVEN: Block on kqueue events with timeout.
217-
WakeReason event_driven_wait() const {
224+
WakeReason event_driven_wait(const x::telem::TimeSpan max_timeout) const {
218225
struct kevent events[8];
219-
const struct timespec timeout = {0, timing::EVENT_DRIVEN_TIMEOUT.nanoseconds()};
226+
const auto timeout_ns = max_timeout.nanoseconds() > 0
227+
? max_timeout.nanoseconds()
228+
: timing::EVENT_DRIVEN_TIMEOUT.nanoseconds();
229+
const auto timeout = ns_to_timespec(timeout_ns);
220230
const int n = kevent(this->kqueue_fd_, nullptr, 0, events, 8, &timeout);
221231

222232
if (n > 0) return this->classify_events(events, n);
@@ -237,6 +247,10 @@ class DarwinLoop final : public Loop {
237247
return WakeReason::Shutdown;
238248
}
239249

250+
static constexpr timespec ns_to_timespec(const int64_t ns) {
251+
return {ns / 1'000'000'000, ns % 1'000'000'000};
252+
}
253+
240254
Config config_;
241255
int kqueue_fd_ = -1;
242256
bool kqueue_timer_enabled_ = false;

arc/cpp/runtime/loop/loop_linux.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ class LinuxLoop final : public Loop {
3030

3131
~LinuxLoop() override { this->close_fds(); }
3232

33-
WakeReason wait(x::breaker::Breaker &breaker) override {
33+
WakeReason wait(
34+
x::breaker::Breaker &breaker,
35+
x::telem::TimeSpan max_timeout = x::telem::TimeSpan(0)
36+
) override {
3437
if (this->epoll_fd_ == -1) return WakeReason::Shutdown;
3538

3639
switch (this->config_.mode) {
@@ -39,12 +42,12 @@ class LinuxLoop final : public Loop {
3942
case ExecutionMode::HIGH_RATE:
4043
return this->high_rate_wait(breaker);
4144
case ExecutionMode::RT_EVENT:
42-
return this->event_driven_wait(true);
45+
return this->event_driven_wait(true, max_timeout);
4346
case ExecutionMode::HYBRID:
44-
return this->hybrid_wait(breaker);
47+
return this->hybrid_wait(breaker, max_timeout);
4548
case ExecutionMode::AUTO:
4649
case ExecutionMode::EVENT_DRIVEN:
47-
return this->event_driven_wait(true);
50+
return this->event_driven_wait(true, max_timeout);
4851
}
4952
return WakeReason::Shutdown;
5053
}
@@ -210,10 +213,13 @@ class LinuxLoop final : public Loop {
210213
return WakeReason::Timer;
211214
}
212215

213-
WakeReason event_driven_wait(bool blocking) {
216+
WakeReason event_driven_wait(bool blocking, const x::telem::TimeSpan max_timeout) {
214217
struct epoll_event events[2];
215-
const int timeout_ms = blocking ? timing::EVENT_DRIVEN_TIMEOUT.milliseconds()
218+
const int default_ms = blocking ? timing::EVENT_DRIVEN_TIMEOUT.milliseconds()
216219
: timing::POLL_TIMEOUT.milliseconds();
220+
const int timeout_ms = max_timeout.nanoseconds() > 0
221+
? static_cast<int>(max_timeout.milliseconds())
222+
: default_ms;
217223
const int n = epoll_wait(this->epoll_fd_, events, 2, timeout_ms);
218224

219225
if (n > 0) return this->consume_events(events, n);
@@ -223,7 +229,10 @@ class LinuxLoop final : public Loop {
223229
return WakeReason::Shutdown;
224230
}
225231

226-
WakeReason hybrid_wait(const x::breaker::Breaker &breaker) {
232+
WakeReason hybrid_wait(
233+
const x::breaker::Breaker &breaker,
234+
const x::telem::TimeSpan max_timeout
235+
) {
227236
const auto spin_start = std::chrono::steady_clock::now();
228237
const auto spin_duration = std::chrono::nanoseconds(
229238
this->config_.spin_duration.nanoseconds()
@@ -238,7 +247,9 @@ class LinuxLoop final : public Loop {
238247
if (n > 0) return this->consume_events(events, n);
239248
}
240249

241-
const int timeout_ms = timing::HYBRID_BLOCK_TIMEOUT.milliseconds();
250+
const int timeout_ms = max_timeout.nanoseconds() > 0
251+
? static_cast<int>(max_timeout.milliseconds())
252+
: timing::HYBRID_BLOCK_TIMEOUT.milliseconds();
242253
const int n = epoll_wait(this->epoll_fd_, events, 2, timeout_ms);
243254
if (n > 0) return this->consume_events(events, n);
244255
return WakeReason::Timeout;

arc/cpp/runtime/loop/loop_polling.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ class PollingLoop final : public Loop {
4646

4747
~PollingLoop() override { this->timer_.reset(); }
4848

49-
WakeReason wait(x::breaker::Breaker &breaker) override {
49+
WakeReason wait(
50+
x::breaker::Breaker &breaker,
51+
x::telem::TimeSpan max_timeout = x::telem::TimeSpan(0)
52+
) override {
5053
if (!this->started_) return WakeReason::Shutdown;
5154

5255
if (this->config_.interval.nanoseconds() > 0 && this->timer_) {
@@ -56,9 +59,12 @@ class PollingLoop final : public Loop {
5659
)
5760
.count();
5861

59-
if (elapsed < this->config_.interval.nanoseconds()) {
60-
const int64_t remaining_ns = this->config_.interval.nanoseconds() -
61-
elapsed;
62+
auto interval_ns = this->config_.interval.nanoseconds();
63+
if (max_timeout.nanoseconds() > 0)
64+
interval_ns = std::min(interval_ns, max_timeout.nanoseconds());
65+
66+
if (elapsed < interval_ns) {
67+
const int64_t remaining_ns = interval_ns - elapsed;
6268

6369
switch (this->config_.mode) {
6470
case ExecutionMode::BUSY_WAIT:
@@ -78,7 +84,9 @@ class PollingLoop final : public Loop {
7884
this->last_tick_ = now;
7985
}
8086
} else {
81-
if (this->config_.mode == ExecutionMode::BUSY_WAIT) {
87+
if (max_timeout.nanoseconds() > 0) {
88+
std::this_thread::sleep_for(max_timeout.chrono());
89+
} else if (this->config_.mode == ExecutionMode::BUSY_WAIT) {
8290
std::this_thread::sleep_for(x::telem::MICROSECOND.chrono());
8391
} else {
8492
std::this_thread::sleep_for(timing::HIGH_RATE_POLL_INTERVAL.chrono());

arc/cpp/runtime/loop/loop_test.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,3 +809,88 @@ TEST(WakeReasonTest, DistinguishesTimerFromInputWhenBothConfigured) {
809809

810810
breaker.stop();
811811
}
812+
813+
/// @brief EVENT_DRIVEN with max_timeout should wake after max_timeout.
814+
TEST(MaxTimeoutTest, EventDriven_WakesAfterMaxTimeout) {
815+
Config config;
816+
config.mode = ExecutionMode::EVENT_DRIVEN;
817+
config.interval = x::telem::TimeSpan(0);
818+
819+
const auto loop = ASSERT_NIL_P(create(config));
820+
821+
x::breaker::Breaker breaker;
822+
823+
const auto sw = x::telem::Stopwatch();
824+
const auto reason = loop->wait(breaker, 20 * x::telem::MILLISECOND);
825+
826+
const auto elapsed = sw.elapsed();
827+
EXPECT_GE(elapsed, 15 * x::telem::MILLISECOND);
828+
EXPECT_LE(elapsed, test_timing::TIMER_UPPER_BOUND);
829+
EXPECT_EQ(reason, WakeReason::Timeout);
830+
}
831+
832+
/// @brief max_timeout should override a longer configured interval.
833+
TEST(MaxTimeoutTest, EventDriven_MaxTimeoutOverridesLongerInterval) {
834+
Config config;
835+
config.mode = ExecutionMode::EVENT_DRIVEN;
836+
config.interval = 500 * x::telem::MILLISECOND;
837+
838+
const auto loop = ASSERT_NIL_P(create(config));
839+
840+
x::breaker::Breaker breaker;
841+
842+
const auto sw = x::telem::Stopwatch();
843+
loop->wait(breaker, 20 * x::telem::MILLISECOND);
844+
845+
const auto elapsed = sw.elapsed();
846+
EXPECT_GE(elapsed, 15 * x::telem::MILLISECOND);
847+
EXPECT_LE(elapsed, test_timing::TIMER_UPPER_BOUND);
848+
}
849+
850+
/// @brief Input arriving before max_timeout should wake immediately.
851+
TEST(MaxTimeoutTest, EventDriven_InputWakesBeforeMaxTimeout) {
852+
Config config;
853+
config.mode = ExecutionMode::EVENT_DRIVEN;
854+
config.interval = x::telem::TimeSpan(0);
855+
856+
const auto loop = ASSERT_NIL_P(create(config));
857+
858+
auto notifier = x::notify::create();
859+
ASSERT_TRUE(loop->watch(*notifier));
860+
861+
x::breaker::Breaker breaker;
862+
863+
std::atomic<WakeReason> reason{WakeReason::Shutdown};
864+
std::thread waiter([&]() {
865+
reason.store(loop->wait(breaker, 500 * x::telem::MILLISECOND));
866+
});
867+
868+
std::this_thread::sleep_for(test_timing::THREAD_STARTUP.chrono());
869+
notifier->signal();
870+
waiter.join();
871+
872+
EXPECT_EQ(reason.load(), WakeReason::Input);
873+
}
874+
875+
/// @brief HYBRID mode with max_timeout should use it for the blocking phase.
876+
TEST(MaxTimeoutTest, Hybrid_MaxTimeoutConstrainsBlockPhase) {
877+
Config config;
878+
config.mode = ExecutionMode::HYBRID;
879+
config.interval = x::telem::TimeSpan(0);
880+
config.spin_duration = 50 * x::telem::MICROSECOND;
881+
882+
const auto loop = ASSERT_NIL_P(create(config));
883+
884+
x::breaker::Breaker breaker;
885+
breaker.start();
886+
887+
const auto sw = x::telem::Stopwatch();
888+
const auto reason = loop->wait(breaker, 20 * x::telem::MILLISECOND);
889+
890+
const auto elapsed = sw.elapsed();
891+
EXPECT_GE(elapsed, 15 * x::telem::MILLISECOND);
892+
EXPECT_LE(elapsed, test_timing::TIMER_UPPER_BOUND);
893+
EXPECT_EQ(reason, WakeReason::Timeout);
894+
895+
breaker.stop();
896+
}

arc/cpp/runtime/loop/loop_windows.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ class WindowsLoop final : public Loop {
3434

3535
~WindowsLoop() override { this->close_handles(); }
3636

37-
WakeReason wait(x::breaker::Breaker &breaker) override {
37+
WakeReason wait(
38+
x::breaker::Breaker &breaker,
39+
x::telem::TimeSpan max_timeout = x::telem::TimeSpan(0)
40+
) override {
3841
if (this->wake_event_ == NULL) return WakeReason::Shutdown;
3942

4043
switch (this->config_.mode) {
@@ -43,12 +46,12 @@ class WindowsLoop final : public Loop {
4346
case ExecutionMode::HIGH_RATE:
4447
return this->high_rate_wait(breaker);
4548
case ExecutionMode::RT_EVENT:
46-
return this->event_driven_wait(false);
49+
return this->event_driven_wait(false, max_timeout);
4750
case ExecutionMode::HYBRID:
48-
return this->hybrid_wait(breaker);
51+
return this->hybrid_wait(breaker, max_timeout);
4952
case ExecutionMode::AUTO:
5053
case ExecutionMode::EVENT_DRIVEN:
51-
return this->event_driven_wait(true);
54+
return this->event_driven_wait(true, max_timeout);
5255
}
5356
return WakeReason::Shutdown;
5457
}
@@ -178,18 +181,21 @@ class WindowsLoop final : public Loop {
178181
return WakeReason::Timer;
179182
}
180183

181-
WakeReason event_driven_wait(bool blocking) {
184+
WakeReason event_driven_wait(bool blocking, const x::telem::TimeSpan max_timeout) {
182185
HANDLE handles[3];
183186
const DWORD count = this->build_handles(handles);
184187
if (count == 0) return WakeReason::Shutdown;
185188

186-
const DWORD timeout_ms = blocking
189+
const DWORD default_ms = blocking
187190
? static_cast<DWORD>(
188191
timing::EVENT_DRIVEN_TIMEOUT.milliseconds()
189192
)
190193
: static_cast<DWORD>(
191194
timing::HYBRID_BLOCK_TIMEOUT.milliseconds()
192195
);
196+
const DWORD timeout_ms = max_timeout.nanoseconds() > 0
197+
? static_cast<DWORD>(max_timeout.milliseconds())
198+
: default_ms;
193199

194200
const DWORD result = WaitForMultipleObjects(count, handles, FALSE, timeout_ms);
195201
if (result == WAIT_TIMEOUT) return WakeReason::Timeout;
@@ -200,7 +206,8 @@ class WindowsLoop final : public Loop {
200206
return this->classify_result(result, handles);
201207
}
202208

203-
WakeReason hybrid_wait(x::breaker::Breaker &breaker) {
209+
WakeReason
210+
hybrid_wait(x::breaker::Breaker &breaker, const x::telem::TimeSpan max_timeout) {
204211
HANDLE handles[3];
205212
const DWORD count = this->build_handles(handles);
206213
if (count == 0) return WakeReason::Shutdown;
@@ -218,9 +225,11 @@ class WindowsLoop final : public Loop {
218225
return this->classify_result(result, handles);
219226
}
220227

221-
const DWORD timeout_ms = static_cast<DWORD>(
222-
timing::HYBRID_BLOCK_TIMEOUT.milliseconds()
223-
);
228+
const DWORD timeout_ms = max_timeout.nanoseconds() > 0
229+
? static_cast<DWORD>(max_timeout.milliseconds())
230+
: static_cast<DWORD>(
231+
timing::HYBRID_BLOCK_TIMEOUT.milliseconds()
232+
);
224233
const DWORD result = WaitForMultipleObjects(count, handles, FALSE, timeout_ms);
225234
if (result == WAIT_TIMEOUT) return WakeReason::Timeout;
226235
if (result < WAIT_OBJECT_0 + count)

arc/cpp/runtime/node/node.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ struct Context {
3232
/// Time-based nodes should only fire when reason is TimerTick.
3333
RunReason reason;
3434
std::function<void(const std::string &output_param)> mark_changed;
35+
std::function<void()> mark_self_changed;
36+
std::function<void(x::telem::TimeSpan)> set_deadline;
3537
std::function<void(const x::errors::Error &)> report_error;
3638
std::function<void()> activate_stage;
3739
};

0 commit comments

Comments
 (0)