Skip to content

Commit e7454dd

Browse files
committed
Use Linsang response completion callbacks
1 parent e9e6551 commit e7454dd

3 files changed

Lines changed: 85 additions & 69 deletions

File tree

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
.minimum_zig_version = "0.16.0",
66
.dependencies = .{
77
.linsang = .{
8-
.hash = "Linsang-0.0.0-SZhixM2mBgBduGnOKEercDaJfHwmo4LfNMlXcyk0DGUa",
9-
.url = "https://github.com/jinzhongjia/Linsang/archive/3b50417e3ddb7a0651a8dd8b7154f26c4d4e5608.tar.gz",
8+
.hash = "Linsang-0.0.0-SZhixF2vBgDJnOE8rrcEh9Tz-016h12Yc4KntI9aghj4",
9+
.url = "https://github.com/jinzhongjia/Linsang/archive/d625dcb8ddad13bd26d4b8621f15fcaad4305eef.tar.gz",
1010
},
1111
},
1212
.paths = .{

docs/PURE_ZIG_REFACTOR.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -473,15 +473,11 @@ zig build -Dtarget=aarch64-macos
473473

474474
1. **Linsang peer lifecycle:** The required primitive exists. zig-webui must
475475
pair `clone` and `deinit` and must not retain `*Connection`.
476-
2. **Linsang static-response lifecycle:** Replaced directory handles remain
477-
open until shutdown so in-flight responses stay valid. Linsang
478-
[issue #2](https://github.com/jinzhongjia/Linsang/issues/2) tracks a
479-
completion callback for earlier release.
480-
3. **Strict bridge protocol lengths:** The Zig parser must treat WebSocket data
476+
2. **Strict bridge protocol lengths:** The Zig parser must treat WebSocket data
481477
as untrusted and must not copy C's NUL-scanning behavior.
482-
4. **Cross-platform browser behavior:** Guarantee URL opening first, then add
478+
3. **Cross-platform browser behavior:** Guarantee URL opening first, then add
483479
platform-specific app-window flags.
484-
5. **WebView is outside the core rewrite:** If required later, separately
480+
4. **WebView is outside the core rewrite:** If required later, separately
485481
decide whether system framework or C ABI linking is acceptable. It must not
486482
block the pure Zig browser version.
487483

src/app.zig

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -243,24 +243,76 @@ const SelectedClient = struct {
243243
};
244244

245245
const DirectoryContent = struct {
246+
gpa: std.mem.Allocator,
246247
path: []u8,
247248
dir: ?std.Io.Dir = null,
249+
io: ?std.Io = null,
250+
references: std.atomic.Value(usize) = .init(1),
251+
252+
fn init(
253+
gpa: std.mem.Allocator,
254+
path: []const u8,
255+
) !*DirectoryContent {
256+
if (path.len == 0) return error.InvalidDirectory;
257+
const content = try gpa.create(DirectoryContent);
258+
errdefer gpa.destroy(content);
259+
content.* = .{
260+
.gpa = gpa,
261+
.path = try gpa.dupe(u8, path),
262+
};
263+
return content;
264+
}
265+
266+
fn retain(self: *DirectoryContent) void {
267+
const previous = self.references.fetchAdd(1, .monotonic);
268+
std.debug.assert(previous > 0 and previous < std.math.maxInt(usize));
269+
}
270+
271+
fn release(self: *DirectoryContent) void {
272+
const previous = self.references.fetchSub(1, .release);
273+
std.debug.assert(previous > 0);
274+
if (previous != 1) return;
275+
_ = self.references.load(.acquire);
276+
self.close();
277+
self.gpa.free(self.path);
278+
self.gpa.destroy(self);
279+
}
280+
281+
fn open(self: *DirectoryContent, io: std.Io) !void {
282+
std.debug.assert(self.dir == null and self.io == null);
283+
self.dir = if (std.fs.path.isAbsolute(self.path))
284+
try std.Io.Dir.openDirAbsolute(io, self.path, .{
285+
.follow_symlinks = false,
286+
})
287+
else
288+
try std.Io.Dir.cwd().openDir(io, self.path, .{
289+
.follow_symlinks = false,
290+
});
291+
self.io = io;
292+
}
293+
294+
fn close(self: *DirectoryContent) void {
295+
if (self.dir) |dir| {
296+
dir.close(self.io.?);
297+
self.dir = null;
298+
self.io = null;
299+
} else {
300+
std.debug.assert(self.io == null);
301+
}
302+
}
248303
};
249304

250305
const StoredContent = union(enum) {
251306
html: []u8,
252-
directory: DirectoryContent,
307+
directory: *DirectoryContent,
253308
custom: CustomResource,
254309
external_url: []u8,
255310

256311
fn init(gpa: std.mem.Allocator, content: Content) !StoredContent {
257312
return switch (content) {
258313
.html => |html| .{ .html = try gpa.dupe(u8, html) },
259-
.directory => |path| blk: {
260-
if (path.len == 0) return error.InvalidDirectory;
261-
break :blk .{ .directory = .{
262-
.path = try gpa.dupe(u8, path),
263-
} };
314+
.directory => |path| .{
315+
.directory = try DirectoryContent.init(gpa, path),
264316
},
265317
.custom => |custom| .{ .custom = custom },
266318
.external_url => |url| blk: {
@@ -273,10 +325,7 @@ const StoredContent = union(enum) {
273325
fn deinit(self: *StoredContent, gpa: std.mem.Allocator) void {
274326
switch (self.*) {
275327
.html => |html| gpa.free(html),
276-
.directory => |directory| {
277-
std.debug.assert(directory.dir == null);
278-
gpa.free(directory.path);
279-
},
328+
.directory => |directory| directory.release(),
280329
.custom => {},
281330
.external_url => |url| gpa.free(url),
282331
}
@@ -285,27 +334,14 @@ const StoredContent = union(enum) {
285334

286335
fn openDirectory(self: *StoredContent, io: std.Io) !void {
287336
switch (self.*) {
288-
.directory => |*directory| {
289-
std.debug.assert(directory.dir == null);
290-
directory.dir = if (std.fs.path.isAbsolute(directory.path))
291-
try std.Io.Dir.openDirAbsolute(io, directory.path, .{
292-
.follow_symlinks = false,
293-
})
294-
else
295-
try std.Io.Dir.cwd().openDir(io, directory.path, .{
296-
.follow_symlinks = false,
297-
});
298-
},
337+
.directory => |directory| try directory.open(io),
299338
else => {},
300339
}
301340
}
302341

303-
fn closeDirectory(self: *StoredContent, io: std.Io) void {
342+
fn closeDirectory(self: *StoredContent) void {
304343
switch (self.*) {
305-
.directory => |*directory| if (directory.dir) |dir| {
306-
dir.close(io);
307-
directory.dir = null;
308-
},
344+
.directory => |directory| directory.close(),
309345
else => {},
310346
}
311347
}
@@ -315,7 +351,6 @@ const WindowState = struct {
315351
gpa: std.mem.Allocator,
316352
content: StoredContent,
317353
content_mutex: std.Io.RwLock = .init,
318-
retired_directories: std.ArrayList(StoredContent) = .empty,
319354
limits: Limits,
320355
max_clients: usize,
321356
max_pending_evals: usize,
@@ -349,8 +384,6 @@ const WindowState = struct {
349384
self.clients.deinit(self.gpa);
350385
for (self.bindings.items) |item| self.gpa.free(item.name);
351386
self.bindings.deinit(self.gpa);
352-
std.debug.assert(self.retired_directories.items.len == 0);
353-
self.retired_directories.deinit(self.gpa);
354387
self.content.deinit(self.gpa);
355388
self.gpa.destroy(self);
356389
}
@@ -363,35 +396,14 @@ const WindowState = struct {
363396
var replacement = try StoredContent.init(self.gpa, content);
364397
errdefer replacement.deinit(self.gpa);
365398
try replacement.openDirectory(io);
366-
errdefer replacement.closeDirectory(io);
399+
errdefer replacement.closeDirectory();
367400

368401
self.content_mutex.lockUncancelable(io);
369402
defer self.content_mutex.unlock(io);
370-
const retire_directory = switch (self.content) {
371-
.directory => true,
372-
else => false,
373-
};
374-
if (retire_directory)
375-
try self.retired_directories.ensureUnusedCapacity(self.gpa, 1);
376403

377404
var previous = self.content;
378405
self.content = replacement;
379-
if (retire_directory) {
380-
// ponytail: Linsang finishes a static-file action after the request
381-
// callback returns. Retire handles until stop unless it gains a
382-
// response-completion callback.
383-
self.retired_directories.appendAssumeCapacity(previous);
384-
} else {
385-
previous.deinit(self.gpa);
386-
}
387-
}
388-
389-
fn releaseRetiredDirectories(self: *WindowState, io: std.Io) void {
390-
for (self.retired_directories.items) |*content| {
391-
content.closeDirectory(io);
392-
content.deinit(self.gpa);
393-
}
394-
self.retired_directories.clearRetainingCapacity();
406+
previous.deinit(self.gpa);
395407
}
396408

397409
fn binding(self: *WindowState, name: []const u8) ?Binding {
@@ -1534,7 +1546,7 @@ pub const App = struct {
15341546
if (binding.name.len > window.limits.max_binding_name_size)
15351547
return error.BindingNameTooLarge;
15361548
}
1537-
errdefer self.closeDirectories(io);
1549+
errdefer self.closeDirectories();
15381550
try self.openDirectories(io);
15391551
if (self.options.tls) |tls| {
15401552
self.tls_auth = try Linsang.tls.CertKeyPair.fromSlice(
@@ -1611,9 +1623,8 @@ pub const App = struct {
16111623
try window.content.openDirectory(io);
16121624
}
16131625

1614-
fn closeDirectories(self: *App, io: std.Io) void {
1615-
for (self.windows.items) |window|
1616-
window.content.closeDirectory(io);
1626+
fn closeDirectories(self: *App) void {
1627+
for (self.windows.items) |window| window.content.closeDirectory();
16171628
}
16181629

16191630
fn hasWindow(self: *const App, state: *WindowState) bool {
@@ -1658,9 +1669,7 @@ pub const Running = struct {
16581669
try self.inner.stop();
16591670
for (self.app.windows.items) |window|
16601671
window.cancelEvents(self.inner.io);
1661-
self.app.closeDirectories(self.inner.io);
1662-
for (self.app.windows.items) |window|
1663-
window.releaseRetiredDirectories(self.inner.io);
1672+
self.app.closeDirectories();
16641673
self.app.deinitTls();
16651674
self.stopped = true;
16661675
self.app.started = false;
@@ -1684,6 +1693,12 @@ fn appFrom(user_data: ?*anyopaque) *App {
16841693
return @ptrCast(@alignCast(user_data.?));
16851694
}
16861695

1696+
fn releaseStaticDirectory(user_data: ?*anyopaque) void {
1697+
const directory: *DirectoryContent =
1698+
@ptrCast(@alignCast(user_data.?));
1699+
directory.release();
1700+
}
1701+
16871702
fn failResponse(response: *Linsang.Response) Linsang.Action {
16881703
response.reset();
16891704
response.status = .internal_server_error;
@@ -1870,7 +1885,12 @@ fn onRequest(
18701885
// ponytail: Linsang StaticFiles has no mount prefix yet. Rewrite
18711886
// only the validated path slice; use strip_prefix when available.
18721887
@constCast(request).path = request.path[capability_len + 1 ..];
1873-
break :blk .{ .files = .{ .dir = dir } };
1888+
directory.retain();
1889+
break :blk .{ .files = .{
1890+
.dir = dir,
1891+
.on_complete = releaseStaticDirectory,
1892+
.user_data = directory,
1893+
} };
18741894
},
18751895
.custom => |custom| blk: {
18761896
custom.handler(

0 commit comments

Comments
 (0)