Skip to content

Commit 8a07ea9

Browse files
committed
one day i will stop
1 parent f871571 commit 8a07ea9

26 files changed

Lines changed: 1378 additions & 623 deletions

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ debug = true
2323
lto = "fat"
2424

2525
[dependencies]
26+
rocksdb = { version = "0.24", default-features = false, features = ["lz4"] }
2627
rusqlite = { version = "0.34.0", features = ["bundled"] }
2728
clap = { version = "^4", features = ["derive"] }
29+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2830
crossbeam-channel = "0.5.15"
2931
modular-bitfield = "^0.13"
3032
derive_builder = "^0.20"
@@ -40,14 +42,12 @@ num_cpus = "^1"
4042
anyhow = "^1"
4143
bitvec = "^1"
4244
regex = "^1"
43-
rocksdb = { version = "0.24", default-features = false, features = ["lz4"] }
4445
ratatui = "0.28"
4546
crossterm = "0.28"
4647
tracing = "0.1"
47-
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
4848
ordered-float = "4.5"
4949

5050
[dev-dependencies]
5151
strum_macros = "0.26"
52-
strum = "0.26"
5352
tempfile = "3"
53+
strum = "0.26"

RUNNING-BUG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Problem Statement
44

5-
Tasks remain in RUNNING state for astronomical periods (10+ seconds) after finishing work. The task's `tick()` method at forward.rs:260 gets called repeatedly even though it yields `Suspended` each time.
5+
Tasks remain in RUNNING state for (relatively) astronomical periods (10+ seconds) after finishing work (progress stops being made). The task's `tick()` method at forward.rs:260 gets called repeatedly even though it yields with intention `Suspended` each time.
66

77
## Experimentally Verified Observations
88

@@ -57,7 +57,7 @@ Tasks remain in RUNNING state for astronomical periods (10+ seconds) after finis
5757

5858
### Isolation Constraints
5959

60-
**Happens with single ForwardTask**
60+
**Happens EVEN with single ForwardTask**
6161
- Only one task is ever registered with the scheduler
6262
- No children are spawned by the task
6363
- Zero-by assigns all states to component 0 for small games
@@ -87,23 +87,26 @@ Tasks remain in RUNNING state for astronomical periods (10+ seconds) after finis
8787
**Bug is practically deterministic**
8888
- Consistently reproduces under the same conditions
8989
- Not a random race condition
90+
- Even the timing (of how long it remains running after progress is no longer being made) is consistent
9091

9192
### Game-Specific Trigger
9293

9394
**Requires '1' in the choices list**
9495
- `2-100000-1` (choices=[1]) exhibits the bug
9596
- `2-100000-2` (choices=[2]) does NOT exhibit the bug
9697
- The ability to remove exactly 1 element is necessary for the bug to occur
98+
- It may be useful to understanad the game zero-by to debug
9799

98100
**Why this matters for zero-by:**
99101
- When '1' is in choices, states like (1, player0) and (1, player1) are reachable non-terminal states
100102
- When only '2' is in choices, state (1, player) is either unreachable or terminal
101103
- This affects the depth and structure of the game graph
102104

103105
**Component assignment:**
104-
- Zero-by uses `elements / 1000000` for component assignment
106+
- Zero-by uses `elements / big_number` for component assignment
105107
- For small games (e.g., 2-100000-1), all states map to component 0
106108
- This guarantees only one ForwardTask exists with no children spawned
109+
- Even in this case the bug occurs
107110

108111
## Why These Observations Matter
109112

@@ -114,3 +117,7 @@ Tasks remain in RUNNING state for astronomical periods (10+ seconds) after finis
114117
5. The proportionality to states explored suggests the loop iterates once per previously explored state
115118
6. The game-specific trigger ('1' in choices) suggests something about the game graph structure matters
116119
7. The eventually-completes behavior means there's a counter or accumulator that eventually drains
120+
121+
## Performance Impact
122+
123+
A big percentage of scheduler time is wasted as a result of this bug.

src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ where
121121
.build()?;
122122

123123
let about = format!("Forward pass of variant {}", ruleset.name());
124-
let task = scheduler::Task::builder()
124+
let task = TaskBuilder::default()
125125
.executable(executable)
126126
.dependencies(std::collections::HashSet::new())
127127
.retriable(true)
@@ -137,7 +137,6 @@ where
137137

138138
let policy = CriticalPathPolicyBuilder::default().build()?;
139139
let runner = ThreadPoolRunnerBuilder::default().build()?;
140-
141140
let mut orchestrator = Orchestrator::builder()
142141
.runner(runner)
143142
.policy(policy)

src/scheduler/core/context.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ pub struct Ready;
3131

3232
/// Helper for handling tasks that can be in either Running or Preempting state.
3333
pub enum Either<L, R> {
34-
Running(L),
3534
Preempting(R),
35+
Running(L),
3636
}
3737

3838
/// Type-erased context for HashMap storage.
@@ -61,8 +61,6 @@ pub struct TaskContext<S> {
6161

6262
/* IMPLEMENTATIONS */
6363

64-
/* CONSTRUCTORS */
65-
6664
impl TaskContext<Ready> {
6765
pub(in crate::scheduler) fn ready(
6866
executable: Box<dyn Executable>,

src/scheduler/logger/dashboard/collect.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ use crate::scheduler::TaskState;
1212

1313
use super::SortOrder;
1414
use super::TaskFilter;
15-
use super::format::format_weight;
15+
use super::support::format::format_weight;
1616

1717
/* IMPLEMENTATIONS */
1818

19+
fn compute_progress_percent(ctx: &TaskContextSnapshot) -> Option<f64> {
20+
ctx.progress.and_then(|p| {
21+
ctx.size
22+
.map(|s| p as f64 / s as f64)
23+
})
24+
}
25+
1926
pub fn collect<'a>(
2027
snapshot: &'a SchedulerSnapshot,
2128
filters: &[TaskFilter],
@@ -43,15 +50,9 @@ pub fn sort(
4350
});
4451
},
4552
SortOrder::Progress => {
46-
let pct = |ctx: &TaskContextSnapshot| {
47-
ctx.progress.and_then(|p| {
48-
ctx.size
49-
.map(|s| p as f64 / s as f64)
50-
})
51-
};
5253
tasks.sort_by(|a, b| {
53-
pct(b.1)
54-
.partial_cmp(&pct(a.1))
54+
compute_progress_percent(b.1)
55+
.partial_cmp(&compute_progress_percent(a.1))
5556
.unwrap_or(Ordering::Equal)
5657
.then_with(|| a.0.cmp(&b.0))
5758
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! Task state breakdown bar component.
2+
3+
use ratatui::widgets::Paragraph;
4+
use ratatui::layout::Rect;
5+
use ratatui::style::Color;
6+
use ratatui::style::Style;
7+
use ratatui::text::Line;
8+
use ratatui::text::Span;
9+
use ratatui::Frame;
10+
11+
use crate::scheduler::SchedulerSnapshot;
12+
13+
use super::super::support::state::count_states;
14+
use super::super::support::state::BarSegments;
15+
use super::super::support::state::StateCounts;
16+
17+
/* IMPLEMENTATIONS */
18+
19+
pub fn render(frame: &mut Frame, area: Rect, snapshot: &SchedulerSnapshot) {
20+
let counts = count_states(snapshot);
21+
let width = area.width as usize;
22+
23+
let lines = vec![
24+
status_line(&counts),
25+
render_state_bar(&counts, width),
26+
];
27+
28+
frame.render_widget(Paragraph::new(lines), area);
29+
}
30+
31+
fn styled_span(label: &'static str, color: Color) -> Span<'static> {
32+
Span::styled(label, Style::default().fg(color))
33+
}
34+
35+
fn status_line(counts: &StateCounts) -> Line<'static> {
36+
let dot = styled_span("• ", Color::White);
37+
38+
Line::from(vec![
39+
styled_span(" Running: ", Color::Green),
40+
Span::raw(format!("{} ", counts.running)),
41+
dot.clone(),
42+
styled_span("Ready: ", Color::Blue),
43+
Span::raw(format!("{} ", counts.ready)),
44+
dot.clone(),
45+
styled_span("Waiting: ", Color::Cyan),
46+
Span::raw(format!("{} ", counts.waiting)),
47+
dot.clone(),
48+
styled_span("Suspended: ", Color::Gray),
49+
Span::raw(format!("{} ", counts.suspended)),
50+
dot,
51+
styled_span("Errors: ", Color::Red),
52+
Span::raw(format!("{}", counts.errors)),
53+
])
54+
}
55+
56+
fn render_state_bar(counts: &StateCounts, width: usize) -> Line<'static> {
57+
Line::from(bar_spans(&bar_segments(counts, width)))
58+
}
59+
60+
fn compute_ratio(count: usize, total: usize, width: usize) -> f64 {
61+
((count as f64 / total as f64) * width as f64).round()
62+
}
63+
64+
fn bar_segments(counts: &StateCounts, width: usize) -> BarSegments {
65+
let total = counts.suspended
66+
+ counts.running
67+
+ counts.waiting
68+
+ counts.errors
69+
+ counts.ready;
70+
71+
if total == 0 {
72+
return BarSegments {
73+
errors: width,
74+
suspended: 0,
75+
running: 0,
76+
waiting: 0,
77+
ready: 0,
78+
};
79+
}
80+
81+
let suspended = compute_ratio(counts.suspended, total, width) as usize;
82+
let running = compute_ratio(counts.running, total, width) as usize;
83+
let waiting = compute_ratio(counts.waiting, total, width) as usize;
84+
let errors = compute_ratio(counts.errors, total, width) as usize;
85+
let ready = compute_ratio(counts.ready, total, width) as usize;
86+
87+
let used = suspended + running + waiting + errors + ready;
88+
let adjust = width.saturating_sub(used);
89+
90+
BarSegments {
91+
errors: errors + adjust,
92+
suspended,
93+
running,
94+
waiting,
95+
ready,
96+
}
97+
}
98+
99+
fn bar_span(count: usize, color: Color) -> Span<'static> {
100+
Span::styled("▃".repeat(count), Style::default().fg(color))
101+
}
102+
103+
fn bar_spans(seg: &BarSegments) -> Vec<Span<'static>> {
104+
vec![
105+
bar_span(seg.running, Color::Green),
106+
bar_span(seg.ready, Color::Blue),
107+
bar_span(seg.waiting, Color::Cyan),
108+
bar_span(seg.suspended, Color::Gray),
109+
bar_span(seg.errors, Color::Red),
110+
]
111+
}
File renamed without changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Dashboard UI components.
2+
//!
3+
//! Each component is self-contained and owns its rendering logic.
4+
5+
pub mod breakdown;
6+
pub mod histogram;
7+
pub mod stats;
8+
pub mod title;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! Stats display component.
2+
3+
use ratatui::widgets::Paragraph;
4+
use ratatui::layout::Rect;
5+
use ratatui::text::Line;
6+
use ratatui::Frame;
7+
8+
use crate::scheduler::SchedulerSnapshot;
9+
use crate::scheduler::RunnerSnapshot;
10+
11+
use super::super::support::state::count_states;
12+
13+
/* IMPLEMENTATIONS */
14+
15+
pub fn render(
16+
frame: &mut Frame,
17+
area: Rect,
18+
snapshot: &SchedulerSnapshot,
19+
runner: Option<&RunnerSnapshot>,
20+
) {
21+
let counts = count_states(snapshot);
22+
23+
let workers = runner
24+
.map(|r| {
25+
let cap = r
26+
.capacity
27+
.map(|c| c.to_string())
28+
.unwrap_or_else(|| "∞".to_string());
29+
format!("{}/{}", counts.running, cap)
30+
})
31+
.unwrap_or_else(|| format!("{}", counts.running));
32+
33+
let title = format!(
34+
" Tick: {} │ Workers: {} │ Tasks: {} ",
35+
snapshot.tick,
36+
workers,
37+
snapshot.tasks.len()
38+
);
39+
40+
let lines = vec![Line::from(title)];
41+
42+
frame.render_widget(Paragraph::new(lines), area);
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Title bar component for dashboard.
2+
3+
use ratatui::widgets::Paragraph;
4+
use ratatui::prelude::Stylize;
5+
use ratatui::layout::Rect;
6+
use ratatui::style::Style;
7+
use ratatui::text::Line;
8+
use ratatui::text::Span;
9+
use ratatui::Frame;
10+
11+
/* IMPLEMENTATIONS */
12+
13+
pub fn render(frame: &mut Frame, area: Rect) {
14+
let line = Line::from(vec![
15+
Span::raw(" "),
16+
Span::styled("Nova Scheduler", Style::default().bold()),
17+
Span::raw(" Press 'q' or ESC to exit"),
18+
]);
19+
frame.render_widget(Paragraph::new(line), area);
20+
}

0 commit comments

Comments
 (0)