mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
tripwire: change backing store from ArrayHashMap to EnumMap
This eliminates all allocation from Tripwire.
This commit is contained in:
@@ -829,7 +829,7 @@ test "init error" {
|
||||
for (std.meta.tags(init_tw.FailPoint)) |tag| {
|
||||
const tw = init_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
try testing.expectError(
|
||||
error.OutOfMemory,
|
||||
init(testing.allocator, 32, .grayscale),
|
||||
@@ -847,7 +847,7 @@ test "reserve error" {
|
||||
var atlas = try init(testing.allocator, 32, .grayscale);
|
||||
defer atlas.deinit(testing.allocator);
|
||||
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
try testing.expectError(
|
||||
error.OutOfMemory,
|
||||
atlas.reserve(testing.allocator, 2, 2),
|
||||
@@ -872,7 +872,7 @@ test "grow error" {
|
||||
const old_modified = atlas.modified.load(.monotonic);
|
||||
const old_resized = atlas.resized.load(.monotonic);
|
||||
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
try testing.expectError(
|
||||
error.OutOfMemory,
|
||||
atlas.grow(testing.allocator, atlas.size + 1),
|
||||
|
||||
@@ -484,7 +484,7 @@ test "renderGlyph error after cache insert rolls back cache entry" {
|
||||
// We use OutOfMemory as it's a valid error in the renderGlyph error set.
|
||||
const tw = renderGlyph_tw;
|
||||
defer tw.end(.reset) catch {};
|
||||
try tw.errorAlways(.get_presentation, error.OutOfMemory);
|
||||
tw.errorAlways(.get_presentation, error.OutOfMemory);
|
||||
|
||||
// This should fail due to the tripwire
|
||||
try testing.expectError(
|
||||
@@ -510,7 +510,7 @@ test "init error" {
|
||||
for (std.meta.tags(init_tw.FailPoint)) |tag| {
|
||||
const tw = init_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
|
||||
// Create a resolver for testing - we need to set up a minimal one.
|
||||
// The caller is responsible for cleaning up the resolver if init fails.
|
||||
|
||||
@@ -5170,7 +5170,7 @@ test "PageList init error" {
|
||||
for (std.meta.tags(init_tw.FailPoint)) |tag| {
|
||||
const tw = init_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
try std.testing.expectError(
|
||||
error.OutOfMemory,
|
||||
init(
|
||||
@@ -5187,7 +5187,7 @@ test "PageList init error" {
|
||||
for (std.meta.tags(initPages_tw.FailPoint)) |tag| {
|
||||
const tw = initPages_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAlways(tag, error.OutOfMemory);
|
||||
tw.errorAlways(tag, error.OutOfMemory);
|
||||
|
||||
const cols: size.CellCountInt = if (tag == .page_buf_std) 80 else std_capacity.maxCols().? + 1;
|
||||
try std.testing.expectError(
|
||||
@@ -5207,7 +5207,7 @@ test "PageList init error" {
|
||||
}) |tag| {
|
||||
const tw = initPages_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAfter(tag, error.OutOfMemory, 1);
|
||||
tw.errorAfter(tag, error.OutOfMemory, 1);
|
||||
try std.testing.expectError(
|
||||
error.OutOfMemory,
|
||||
init(
|
||||
|
||||
@@ -9493,7 +9493,7 @@ test "selectionString map allocation failure cleanup" {
|
||||
|
||||
// Trigger allocation failure on toOwnedSlice
|
||||
var map: StringMap = undefined;
|
||||
try selectionString_tw.errorAlways(.copy_map, error.OutOfMemory);
|
||||
selectionString_tw.errorAlways(.copy_map, error.OutOfMemory);
|
||||
const result = s.selectionString(alloc, .{
|
||||
.sel = sel,
|
||||
.map = &map,
|
||||
|
||||
@@ -261,7 +261,7 @@ test "Tabstops: resize alloc failure preserves state" {
|
||||
const original_cols = t.cols;
|
||||
|
||||
// Trigger allocation failure when resizing beyond prealloc
|
||||
try resize_tw.errorAlways(.dynamic_alloc, error.OutOfMemory);
|
||||
resize_tw.errorAlways(.dynamic_alloc, error.OutOfMemory);
|
||||
const result = t.resize(testing.allocator, prealloc_columns * 2);
|
||||
try testing.expectError(error.OutOfMemory, result);
|
||||
try resize_tw.end(.reset);
|
||||
|
||||
@@ -1460,7 +1460,7 @@ test "reloadActive partial history cleanup on appendSlice error" {
|
||||
// that need cleanup.
|
||||
const tw = reloadActive_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAlways(.history_append_existing, error.OutOfMemory);
|
||||
tw.errorAlways(.history_append_existing, error.OutOfMemory);
|
||||
|
||||
// reloadActive is called by select(), which should trigger the error path.
|
||||
// If the bug exists, testing.allocator will report a memory leak
|
||||
@@ -1507,7 +1507,7 @@ test "reloadActive partial history cleanup on loop append error" {
|
||||
// that needs cleanup.
|
||||
const tw = reloadActive_tw;
|
||||
defer tw.end(.reset) catch unreachable;
|
||||
try tw.errorAfter(.history_append_new, error.OutOfMemory, 1);
|
||||
tw.errorAfter(.history_append_new, error.OutOfMemory, 1);
|
||||
|
||||
// reloadActive is called by select(), which should trigger the error path.
|
||||
// If the bug exists, testing.allocator will report a memory leak
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! test "myFunction fails on alloc" {
|
||||
//! try tw.errorAlways(.alloc_buf, error.OutOfMemory);
|
||||
//! tw.errorAlways(.alloc_buf, error.OutOfMemory);
|
||||
//! try std.testing.expectError(error.OutOfMemory, myFunction());
|
||||
//! try tw.end(.reset);
|
||||
//! }
|
||||
@@ -67,7 +67,6 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const log = std.log.scoped(.tripwire);
|
||||
|
||||
@@ -134,8 +133,8 @@ pub fn module(
|
||||
}
|
||||
|
||||
/// The configured tripwires for this module.
|
||||
var tripwires: TripwireMap = .empty;
|
||||
const TripwireMap = std.AutoArrayHashMapUnmanaged(FailPoint, Tripwire);
|
||||
var tripwires: TripwireMap = .{};
|
||||
const TripwireMap = std.EnumMap(FailPoint, Tripwire);
|
||||
const Tripwire = struct {
|
||||
/// Error to return when tripped
|
||||
err: Error,
|
||||
@@ -155,14 +154,6 @@ pub fn module(
|
||||
tripped: bool = false,
|
||||
};
|
||||
|
||||
/// For all allocations we use an allocator that can leak memory
|
||||
/// without reporting it, since this is only used in tests. We don't
|
||||
/// want to use a testing allocator here because that would report
|
||||
/// leaks. Users are welcome to call `deinit` on the module to
|
||||
/// free all memory.
|
||||
const LeakyAllocator = std.heap.DebugAllocator(.{});
|
||||
var alloc_state: LeakyAllocator = .init;
|
||||
|
||||
/// Check for a failure at the given failure point. These should
|
||||
/// be placed directly before the `try` operation that may fail.
|
||||
pub fn check(point: FailPoint) callconv(callingConvention()) Error!void {
|
||||
@@ -187,42 +178,31 @@ pub fn module(
|
||||
}
|
||||
|
||||
/// Mark a failure point to always trip with the given error.
|
||||
pub fn errorAlways(
|
||||
point: FailPoint,
|
||||
err: Error,
|
||||
) Allocator.Error!void {
|
||||
try errorAfter(point, err, 0);
|
||||
pub fn errorAlways(point: FailPoint, err: Error) void {
|
||||
errorAfter(point, err, 0);
|
||||
}
|
||||
|
||||
/// Mark a failure point to trip with the given error after
|
||||
/// the failure point is reached at least `min` times. A value of
|
||||
/// zero is equivalent to `errorAlways`.
|
||||
pub fn errorAfter(
|
||||
point: FailPoint,
|
||||
err: Error,
|
||||
min: usize,
|
||||
) Allocator.Error!void {
|
||||
try tripwires.put(
|
||||
alloc_state.allocator(),
|
||||
point,
|
||||
.{ .err = err, .min = min },
|
||||
);
|
||||
pub fn errorAfter(point: FailPoint, err: Error, min: usize) void {
|
||||
tripwires.put(point, .{ .err = err, .min = min });
|
||||
}
|
||||
|
||||
/// Ends the tripwire session. This will raise an error if there
|
||||
/// were untripped error expectations. The reset mode specifies
|
||||
/// whether memory is reset too. Memory is always reset, even if
|
||||
/// this returns an error.
|
||||
/// whether expectations are reset too. Expectations are always reset,
|
||||
/// even if this returns an error.
|
||||
pub fn end(reset_mode: enum { reset, retain }) error{UntrippedError}!void {
|
||||
var untripped: bool = false;
|
||||
for (tripwires.keys(), tripwires.values()) |key, entry| {
|
||||
if (!entry.tripped) {
|
||||
log.warn("untripped point={t}", .{key});
|
||||
var iter = tripwires.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
if (!entry.value.tripped) {
|
||||
log.warn("untripped point={s}", .{@tagName(entry.key)});
|
||||
untripped = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We always reset memory before failing
|
||||
switch (reset_mode) {
|
||||
.reset => reset(),
|
||||
.retain => {},
|
||||
@@ -231,10 +211,9 @@ pub fn module(
|
||||
if (untripped) return error.UntrippedError;
|
||||
}
|
||||
|
||||
/// Unset all the tripwires and free all allocated memory. You
|
||||
/// should usually call `end` instead.
|
||||
/// Unset all the tripwires. You should usually call `end` instead.
|
||||
pub fn reset() void {
|
||||
tripwires.clearAndFree(alloc_state.allocator());
|
||||
tripwires = .{};
|
||||
}
|
||||
|
||||
/// Our calling convention is inline if our tripwire module is
|
||||
@@ -258,7 +237,7 @@ test {
|
||||
try io.check(.read);
|
||||
|
||||
// Always trip
|
||||
try io.errorAlways(.read, error.OutOfMemory);
|
||||
io.errorAlways(.read, error.OutOfMemory);
|
||||
try testing.expectError(
|
||||
error.OutOfMemory,
|
||||
io.check(.read),
|
||||
@@ -283,7 +262,7 @@ test "module as error set" {
|
||||
test "errorAfter" {
|
||||
const io = module(enum { read, write }, anyerror);
|
||||
// Trip after 2 calls (on the 3rd call)
|
||||
try io.errorAfter(.read, error.OutOfMemory, 2);
|
||||
io.errorAfter(.read, error.OutOfMemory, 2);
|
||||
|
||||
// First two calls succeed
|
||||
try io.check(.read);
|
||||
@@ -298,7 +277,7 @@ test "errorAfter" {
|
||||
|
||||
test "errorAfter untripped error if min not reached" {
|
||||
const io = module(enum { read }, anyerror);
|
||||
try io.errorAfter(.read, error.OutOfMemory, 2);
|
||||
io.errorAfter(.read, error.OutOfMemory, 2);
|
||||
// Only call once, not enough to trip
|
||||
try io.check(.read);
|
||||
// end should fail because tripwire was set but never tripped
|
||||
|
||||
Reference in New Issue
Block a user