Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segmentation fault in ZMessage #8

Open
ritalin opened this issue Jun 17, 2024 · 3 comments
Open

Segmentation fault in ZMessage #8

ritalin opened this issue Jun 17, 2024 · 3 comments
Assignees
Labels
bug Something isn't working

Comments

@ritalin
Copy link

ritalin commented Jun 17, 2024

For sending, if ZMessage is created in stack frame, segmentation fault was reported.

Following code is example.

fn someFunction(allocator: Allocator, socket: *ZSocket, text: []const u8) !void {
    var msg =try  ZMessage.init(allocator, text);
    defer msg.deinit();

    try socket.send(&msg, .{});
} // <---- ZMessage instance is invalidated here

If ZExternalMessage.refRelease is called after exit function, it will be released dangling pointer.

Resolving this issue, it must allocate ZInternalMessage into heap.

For instance: in ZMessage.init method

const ZMessageImpl = union(ZMessageType) {
    Internal: *ZMessageInternal, // <---- hold as pointer
    External: ZMessageExternal,
};

pub const ZMessage = struct {
    impl_: ZMessageImpl,

    /// Creates a message based on the provided data.
    ///
    /// The data is being copied into the message.
    pub fn init(allocator: std.mem.Allocator, d: []const u8) !ZMessage {
+       const impl = try allocator.create(ZMessageInternal);  // <----- heap allocation
+       impl.* = try ZMessageInternal.init(allocator, d);

        return .{ .impl_ = .{
-            .internal = try ZMessageInternal.init(allocator, d),
+            .Internal = impl,
        } };
    }

    // (snip)
};

const ZMessageInternal = struct {
    // (snip) 

    fn refRelease(self: *ZMessageInternal) void {
        const prev = @atomicRmw(usize, &self.refCounter_, .Sub, 1, .seq_cst);

        if (prev == 1) { // it's now zero
            if (self.allocator_) |a| {
                a.free(self.data_);
+               a.destroy(self);  // < ---- suicide safety 
            }
        }
    }
};

Since this ZMessageInternal variant of ZMessage goes on keeping in heap memory, It can release safety.


In addition to this solution, as ZMessage.initUnmanaged does not always pass allocator, it may be need InternalUnmanaged variant of ZMessageImpl.

@fkollmann
Copy link
Contributor

Thanks for reporting the issue! Please give me some time to reproduce the issue and have a look at possible solutions. Also thanks for proposing one solution here.

@fkollmann fkollmann self-assigned this Jun 17, 2024
@fkollmann fkollmann added the bug Something isn't working label Jun 17, 2024
@fnzr
Copy link

fnzr commented Jan 11, 2025

I believe I'm having the same, or similar, issue. It's very possible I'm using it wrong, if so, please advise.
Here's the test:

const std = @import("std");
const zzmq = @import("zzmq");

fn free_with_defer(socket: *zzmq.ZSocket, allocator: std.mem.Allocator) !void {
    const buffer = try allocator.alloc(u8, 16);
    defer allocator.free(buffer);

    var message = try zzmq.ZMessage.initUnmanaged(buffer, null);
    defer message.deinit();

    try socket.send(&message, .{});
}

fn provide_allocator(socket: *zzmq.ZSocket, allocator: std.mem.Allocator) !void {
    const buffer = try allocator.alloc(u8, 16);

    var message = try zzmq.ZMessage.initUnmanaged(buffer, allocator);
    defer message.deinit();

    try socket.send(&message, .{});
}

test "minimal" {
    var context = try zzmq.ZContext.init(std.testing.allocator);
    defer context.deinit();

    var pusher = try zzmq.ZSocket.init(zzmq.ZSocketType.Push, &context);
    defer pusher.deinit();
    try pusher.connect("inproc://1");

    // this causes segfault
    // try free_with_defer(pusher, std.testing.allocator);
    // this causes memory leak
    try provide_allocator(pusher, std.testing.allocator);

    var puller = try zzmq.ZSocket.init(zzmq.ZSocketType.Pull, &context);
    defer puller.deinit();
    try puller.bind("inproc://1");

    var payload_frame = try puller.receive(.{});
    defer payload_frame.deinit();
    const payload = try payload_frame.data();
    std.debug.print("payload: {s}", .{payload});
}

The provided solution in the first post didn't fix it in my case.

Oh, and the same issue happens whether the send is called in a function or not)

@fnzr
Copy link

fnzr commented Jan 11, 2025

Actually I had applied the patch wrong. The proposed solution works, when both freeing with defer and proving the allocator in ZMessage.init. Here's the working sample, with the patch applied:

const std = @import("std");
const zzmq = @import("zzmq");

fn free_with_defer_and_provide_allocator(socket: *zzmq.ZSocket, allocator: std.mem.Allocator) !void {
    const buffer = try allocator.alloc(u8, 16);
    defer allocator.free(buffer);

    var message = try zzmq.ZMessage.init(allocator, buffer);
    defer message.deinit();

    try socket.send(&message, .{});
}

test "minimal" {
    var context = try zzmq.ZContext.init(std.testing.allocator);
    defer context.deinit();

    var pusher = try zzmq.ZSocket.init(zzmq.ZSocketType.Push, &context);
    defer pusher.deinit();
    try pusher.connect("inproc://1");

    try free_with_defer_and_provide_allocator(pusher, std.testing.allocator);

    var puller = try zzmq.ZSocket.init(zzmq.ZSocketType.Pull, &context);
    defer puller.deinit();
    try puller.bind("inproc://1");

    var payload_frame = try puller.receive(.{});
    defer payload_frame.deinit();
    const payload = try payload_frame.data();
    std.debug.print("payload: {any}", .{payload});
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants