Is there any way to make a global variable that is local to each task created by Io.async and Io.concurrent, similar to threadlocal?
For context, I am currently working on a project in which I would need to pass common instances like allocators to almost all functions. In this case, the allocator instance will almost never change, and I would need to pass along the same instance in every function manually. Although I consider it to be unidiomatic, I decided it is better to make these common instances as shared global variables, and have functions that need to pass different instances replace the global instance temporarily. I have made an example to show what exactly I want to do:
const std = @import("std");
const Allocator = std.mem.Allocator;
pub threadlocal var global_allocator: Allocator = undefined;
pub fn main(init: std.process.Init) !void {
global_allocator = init.gpa;
// The allocator is implicitly "passed" to the function.
try foo();
try bar();
}
// The global allocator is replaced for only one function call.
pub fn callWithAllocator(
new_allocator: Allocator,
function: anytype,
args: anytype,
) (@typeInfo(@TypeOf(function)).@"fn".return_type orelse @compileError("Return type is null")) {
const prev_allocator = global_allocator;
global_allocator = new_allocator;
defer global_allocator = prev_allocator;
return @call(.auto, function, args);
}
fn foo() !void {
var buffer: [256]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
// bar() will use the FixedBufferAllocator from above instead of the default init.gpa
try callWithAllocator(fba.allocator(), bar, .{});
var arena = std.heap.ArenaAllocator.init(global_allocator);
defer arena.deinit();
try callWithAllocator(arena.allocator(), bar, .{});
}
fn bar() !void {
std.debug.print("global_allocator: {any:016}\n", .{global_allocator.ptr});
var list = try std.array_list.Aligned(u8, null).initCapacity(global_allocator, 1);
defer list.deinit(global_allocator);
try list.append(global_allocator, 21);
try list.append(global_allocator, 27);
try list.append(global_allocator, 41);
try list.append(global_allocator, 47);
}
However, if I understand the documentation correctly, threadlocal seems to only be meaningful when using actual os-level threads. If the Io implementation uses green threads instead, the global_allocator could change unexpectedly if the thread is interrupted and a context switch happens.
Is there any way to make the Io implementation respect this variable, so each context gets its own local instance?