@@ -243,24 +243,76 @@ const SelectedClient = struct {
243243};
244244
245245const 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
250305const 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+
16871702fn 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