authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-24 16:47:01+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-25 11:48:45+01:00
log5363a81a57b669e43fc790b3318e1a02f967eb15
treeb335e6d238fa34dbfd2d9c67b77c3902ebdb5198
parent3af5f81e11e2fd88fe227b44753e3df0c4dab094

std.heap.FixedBufferAllocator: fix `end_index` memory ordering

This prevents a race between `alloc` and `free` where T1 receives memory from `alloc` that is semantically about to be freed by T2 and still being accessed, but the `free` is already visible to T1. Using acquire-release here guarantees that any `free` is only published after all accesses to the memory being freed have already happened. Co-authored-by: Jacob Young <amazingjacob@gmail.com>

1 files changed, 38 insertions(+), 21 deletions(-)

lib/std/heap/FixedBufferAllocator.zig+38-21
......@@ -137,7 +137,14 @@ fn threadSafeAlloc(ctx: *anyopaque, n: usize, alignment: mem.Alignment, ret_addr
137137 const adjusted_index = cur_end_index + adjust_off;
138138 const new_end_index = adjusted_index + n;
139139 if (new_end_index > self.buffer.len) return null;
140 cur_end_index = @cmpxchgWeak(usize, &self.end_index, cur_end_index, new_end_index, .monotonic, .monotonic) orelse
140 cur_end_index = @cmpxchgWeak(
141 usize,
142 &self.end_index,
143 cur_end_index,
144 new_end_index,
145 .acquire, // acquire any memory that may have been freed
146 .monotonic,
147 ) orelse
141148 return self.buffer[adjusted_index..new_end_index].ptr;
142149 }
143150}
......@@ -154,26 +161,36 @@ fn threadSafeResize(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new
154161 return new_len <= memory.len;
155162 }
156163
157 const new_end_index: usize = new_end_index: {
158 if (memory.len >= new_len) {
159 break :new_end_index cur_end_index - (memory.len - new_len);
160 }
161 if (fba.buffer.len - cur_end_index >= new_len - memory.len) {
162 break :new_end_index cur_end_index + (new_len - memory.len);
163 }
164 return false;
165 };
166 assert(fba.buffer.ptr + new_end_index == memory.ptr + new_len);
164 if (new_len <= memory.len) {
165 const new_end_index = cur_end_index - (memory.len - new_len);
166 assert(fba.buffer.ptr + new_end_index == memory.ptr + new_len);
167
168 _ = @cmpxchgStrong(
169 usize,
170 &fba.end_index,
171 cur_end_index,
172 new_end_index,
173 .release, // release freed memory
174 .monotonic,
175 );
176 return true; // Shrinking allocations should always succeed.
177 }
167178
168 return null == @cmpxchgStrong(
169 usize,
170 &fba.end_index,
171 cur_end_index,
172 new_end_index,
173 .monotonic,
174 .monotonic,
175 ) or
176 new_len <= memory.len; // Shrinking allocations should always succeed.
179 if (fba.buffer.len - cur_end_index >= new_len - memory.len) {
180 const new_end_index = cur_end_index + (new_len - memory.len);
181 assert(fba.buffer.ptr + new_end_index == memory.ptr + new_len);
182
183 return null == @cmpxchgStrong(
184 usize,
185 &fba.end_index,
186 cur_end_index,
187 new_end_index,
188 .acquire, // acquire any memory that may have been freed
189 .monotonic,
190 );
191 }
192
193 return false;
177194}
178195
179196fn threadSafeRemap(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
......@@ -201,7 +218,7 @@ fn threadSafeFree(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, ret_a
201218 &fba.end_index,
202219 cur_end_index,
203220 new_end_index,
204 .monotonic,
221 .release, // release freed memory
205222 .monotonic,
206223 );
207224}