Skip to content

Commit 60df3e7

Browse files
author
Stephen Gutekanst
committed
object: graph: replace switching-consumer pattern with dedicated graph processing thread
Signed-off-by: Stephen Gutekanst <[email protected]>
1 parent d72facd commit 60df3e7

File tree

1 file changed

+20
-22
lines changed

1 file changed

+20
-22
lines changed

src/graph.zig

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ const Op = union(enum) {
104104
/// graph as /operations/ enqueued to a lock-free Multi Producer, Single Consumer (MPSC) FIFO queue.
105105
///
106106
/// When an operation is desired (adding a parent to a child, querying the children or parent of a
107-
/// node, etc.) it is enqueued. Then, if the queue contains entries, that thread becomes the
108-
/// consumer of the MPSC queue temporarily and processes all pending operations in the queue.
107+
/// node, etc.) it is enqueued. Then, a background thread processes all pending operations. Atomics
108+
/// are used to wait for reads to complete, and parallel writes are lock-free.
109109
///
110110
/// The graph uses lock-free pools to manage all nodes internally, eliminating runtime allocations
111111
/// during operation processing.
@@ -130,7 +130,15 @@ pub const Graph = struct {
130130

131131
preallocate_result_list_size: u32,
132132

133+
/// Thread that processes operations from the queue
134+
thread: ?std.Thread = null,
135+
136+
/// Flag to signal the processing thread to stop
137+
should_stop: std.atomic.Value(bool) = .init(false),
138+
133139
/// Initialize the graph with the given pre-allocated space for nodes and operations.
140+
///
141+
/// Spawns a backgroound thread for processing operations to the graph.
134142
pub fn init(
135143
graph: *Graph,
136144
allocator: std.mem.Allocator,
@@ -173,9 +181,13 @@ pub const Graph = struct {
173181
try list.ensureTotalCapacity(allocator, preallocate.result_list_size);
174182
try graph.result_lists.available.append(allocator, list);
175183
}
184+
185+
graph.thread = try std.Thread.spawn(.{ .allocator = allocator }, processThread, .{ graph, allocator });
176186
}
177187

178188
pub fn deinit(graph: *Graph, allocator: std.mem.Allocator) void {
189+
graph.should_stop.store(true, .release);
190+
graph.thread.?.join();
179191
for (graph.result_lists.available.items) |list| {
180192
list.deinit(allocator);
181193
allocator.destroy(list);
@@ -193,20 +205,12 @@ pub const Graph = struct {
193205
return graph.id_to_node.map.get(id);
194206
}
195207

196-
/// Tries to take all queued operations to the graph and, if successful, processes them.
197-
///
198-
/// A different thread which calls processQueue() may beat us to acquiring all of the queued
199-
/// operations, in which case this function may return before they are processed.
200-
fn processQueue(graph: *Graph, allocator: std.mem.Allocator) void {
201-
if (graph.queue.takeAll()) |nodes| {
202-
defer graph.queue.releaseAll(nodes);
203-
204-
// Process the entire chain of nodes
205-
var current: ?*Queue(Op).Node = nodes;
206-
while (current) |node| {
207-
graph.processOp(allocator, node.value);
208-
current = node.next;
209-
}
208+
/// The thread that runs continuously in the background to process queue submissions.
209+
fn processThread(graph: *Graph, allocator: std.mem.Allocator) void {
210+
while (!graph.should_stop.load(.acquire)) {
211+
// Process the entire queue
212+
while (graph.queue.pop()) |op| graph.processOp(allocator, op);
213+
std.Thread.yield() catch {};
210214
}
211215
}
212216

@@ -316,7 +320,6 @@ pub const Graph = struct {
316320
.parent_id = parent_id,
317321
.child_id = child_id,
318322
} });
319-
graph.processQueue(allocator);
320323
}
321324

322325
pub fn removeChild(graph: *Graph, allocator: std.mem.Allocator, parent_id: u64, child_id: u64) Error!void {
@@ -325,7 +328,6 @@ pub const Graph = struct {
325328
.parent_id = parent_id,
326329
.child_id = child_id,
327330
} });
328-
graph.processQueue(allocator);
329331
}
330332

331333
pub fn setParent(graph: *Graph, allocator: std.mem.Allocator, child_id: u64, parent_id: u64) Error!void {
@@ -335,14 +337,12 @@ pub const Graph = struct {
335337
.child_id = child_id,
336338
.parent_id = parent_id,
337339
} });
338-
graph.processQueue(allocator);
339340
}
340341

341342
pub fn removeParent(graph: *Graph, allocator: std.mem.Allocator, child_id: u64) Error!void {
342343
try graph.queue.push(allocator, .{ .remove_parent = .{
343344
.child_id = child_id,
344345
} });
345-
graph.processQueue(allocator);
346346
}
347347

348348
const Results = struct {
@@ -374,7 +374,6 @@ pub const Graph = struct {
374374
} });
375375

376376
while (!done.load(.acquire)) {
377-
graph.processQueue(allocator);
378377
std.Thread.yield() catch {};
379378
}
380379

@@ -429,7 +428,6 @@ pub const Graph = struct {
429428
} });
430429

431430
while (!done.load(.acquire)) {
432-
graph.processQueue(allocator);
433431
std.Thread.yield() catch {};
434432
}
435433

0 commit comments

Comments
 (0)