Skip to content

Conversation

@Techatrix
Copy link
Contributor

When calling async, concurrent or std.Io.Group.async on std.Io.Threaded with a function that has parameters or a return value with extra alignment, it may fail because the internal async closure did not properly create well aligned storage for them.

const std = @import("std");

pub fn main() !void {
    // Depending on which allocator is used, the code may work "by accident". 
    // Using a fixed buffer allocator did consistently reproduce the issue for me
    var buffer: [1024]u8 align(64) = undefined;
    var fba: std.heap.FixedBufferAllocator = .init(buffer[1..]);

    var threaded: std.Io.Threaded = .init(fba.allocator());
    defer threaded.deinit();
    const io = threaded.io();

    var future = io.async(paramWithExtraAlignment, .{.{}});
    future.await(io);
}

fn paramWithExtraAlignment(unused: Align64) void {
    _ = unused;
}

const Align64 = struct {
    unused: u8 align(64) = 0,
};
zig run sample.zig 
thread 5824 panic: incorrect alignment
/home/techatrix/repos/zig/lib/std/Io.zig:1534:46: 0x1163409 in start (std.zig)
            const args_casted: *const Args = @ptrCast(@alignCast(context));
                                             ^
/home/techatrix/repos/zig/lib/std/Io/Threaded.zig:405:16: 0x106af5e in start (std.zig)
        ac.func(ac.contextPointer(), ac.resultPointer());
               ^
/home/techatrix/repos/zig/lib/std/Io/Threaded.zig:188:26: 0x10d9b06 in worker (std.zig)
            closure.start(closure);
                         ^
/home/techatrix/repos/zig/lib/std/Thread.zig:558:13: 0x10b3100 in callFn__anon_16055 (std.zig)
            @call(.auto, f, args);
            ^
/home/techatrix/repos/zig/lib/std/Thread.zig:1534:30: 0x108f070 in entryFn (std.zig)
                return callFn(f, self.fn_args);
                             ^
/home/techatrix/repos/zig/lib/std/os/linux/x86_64.zig:105:5: 0x10b31b5 in clone (std.zig)
    asm volatile (
    ^
Aborted (core dumped)

@squeek502
Copy link
Member

Overlaps with #25817 (cc @castholm)

Copy link
Contributor

@castholm castholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the problem I point out, the differences between this PR and #25817 is that this PR is slightly more efficient with the sizes of closure fields (2 * u8 + 2 * usize vs 3 * usize) but allocates a few more wasted bytes when the context alignment is <= the closure alignemnt.

#25817 also deduplicates the allocation code to a common init() function to avoid having the code paths going out of sync.

I could try to adapt the test to my PR if you think it'd be useful.

Comment on lines 422 to 425
fn resultPointer(ac: *AsyncClosure) [*]u8 {
const base: [*]u8 = @ptrCast(ac);
return base + ac.result_offset;
const closure_size_with_padding = @sizeOf(AsyncClosure) + ac.context_alignment.forward(@alignOf(AsyncClosure));
return @ptrFromInt(ac.result_alignment.forward(@intFromPtr(base + closure_size_with_padding)));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong to me, the code is not aware of the context length. resultPointer() will return a pointer into the middle of the context. AsyncClosure needs to remember either the computed result offset, or the context alignment + context length + result alignment to recompute it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I adjusted it to result offset instead of the total allocated memory for the closure.

@Techatrix Techatrix force-pushed the io-threaded-alignment branch 2 times, most recently from 50f3eed to a00a915 Compare November 9, 2025 23:42
@Techatrix
Copy link
Contributor Author

I could try to adapt the test to my PR if you think it'd be useful.

Feel free to reuse the tests in your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants