@@ -35,6 +35,8 @@ lsp_capabilities: struct {
3535 supports_semantic_tokens_refresh : bool = false ,
3636 supports_inlay_hints_refresh : bool = false ,
3737} = .{},
38+ currently_loading_uris : std .StringArrayHashMapUnmanaged (void ) = .empty ,
39+ wait_for_currently_loading_uri : std.Thread.Condition = .{},
3840
3941pub const Uri = []const u8 ;
4042
@@ -647,6 +649,9 @@ pub fn deinit(self: *DocumentStore) void {
647649 }
648650 self .trigram_stores .deinit (self .allocator );
649651
652+ std .debug .assert (self .currently_loading_uris .count () == 0 );
653+ self .currently_loading_uris .deinit (self .allocator );
654+
650655 if (supports_build_system ) {
651656 for (self .build_files .values ()) | build_file | {
652657 build_file .deinit (self .allocator );
@@ -725,10 +730,35 @@ pub fn getOrLoadHandle(store: *DocumentStore, uri: Uri) ?*Handle {
725730 const tracy_zone = tracy .trace (@src ());
726731 defer tracy_zone .end ();
727732
728- if (store .getHandle (uri )) | handle | return handle ;
733+ {
734+ store .lock .lock ();
735+ defer store .lock .unlock ();
729736
730- const file_contents = store .readUri (uri ) orelse return null ;
737+ while (true ) {
738+ if (store .handles .get (uri )) | handle | return handle ;
739+
740+ const gop = store .currently_loading_uris .getOrPutValue (
741+ store .allocator ,
742+ uri ,
743+ {},
744+ ) catch return null ;
745+
746+ if (! gop .found_existing ) {
747+ break ;
748+ }
749+
750+ var mutex : std.Thread.Mutex = .{};
751+
752+ mutex .lock ();
753+ defer mutex .unlock ();
754+
755+ store .lock .unlock ();
756+ store .wait_for_currently_loading_uri .wait (& mutex );
757+ store .lock .lock ();
758+ }
759+ }
731760
761+ const file_contents = store .readUri (uri ) orelse return null ;
732762 return store .createAndStoreDocument (uri , file_contents ) catch | err | {
733763 log .err ("failed to store document '{s}': {}" , .{ uri , err });
734764 return null ;
@@ -831,6 +861,8 @@ pub fn openDocument(self: *DocumentStore, uri: Uri, text: []const u8) error{OutO
831861 }
832862 gop .value_ptr .* = handle_ptr ;
833863
864+ try self .loadDependencies (handle_ptr .import_uris .items );
865+
834866 if (isBuildFile (uri )) {
835867 log .debug ("Opened document '{s}' (build file)" , .{uri });
836868 } else {
@@ -885,6 +917,7 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) !
885917 try handle .setSource (new_text );
886918 handle .import_uris = try self .collectImportUris (handle );
887919 handle .cimports = try collectCIncludes (self .allocator , handle .tree );
920+ try self .loadDependencies (handle .import_uris .items );
888921}
889922
890923/// Removes a document from the store, unless said document is currently open and
@@ -1551,13 +1584,17 @@ fn createAndStoreDocument(self: *DocumentStore, uri: Uri, text: [:0]const u8) er
15511584 const gop = blk : {
15521585 self .lock .lock ();
15531586 defer self .lock .unlock ();
1554- break :blk try self .handles .getOrPutValue (self .allocator , handle_ptr .uri , handle_ptr );
1587+
1588+ const gop = try self .handles .getOrPutValue (self .allocator , handle_ptr .uri , handle_ptr );
1589+ std .debug .assert (! gop .found_existing );
1590+
1591+ std .debug .assert (self .currently_loading_uris .swapRemove (uri ));
1592+ self .wait_for_currently_loading_uri .broadcast ();
1593+
1594+ break :blk gop ;
15551595 };
15561596
1557- if (gop .found_existing ) {
1558- handle_ptr .deinit ();
1559- self .allocator .destroy (handle_ptr );
1560- }
1597+ try self .loadDependencies (handle_ptr .import_uris .items );
15611598
15621599 if (isBuildFile (gop .value_ptr .* .uri )) {
15631600 log .debug ("Opened document '{s}' (build file)" , .{gop .value_ptr .* .uri });
@@ -1568,6 +1605,50 @@ fn createAndStoreDocument(self: *DocumentStore, uri: Uri, text: [:0]const u8) er
15681605 return gop .value_ptr .* ;
15691606}
15701607
1608+ fn loadDependencies (store : * DocumentStore , import_uris : []const []const u8 ) ! void {
1609+ var not_currently_loading_uris : std .ArrayListUnmanaged ([]const u8 ) =
1610+ try .initCapacity (store .allocator , import_uris .len );
1611+ defer {
1612+ not_currently_loading_uris .deinit (store .allocator );
1613+ }
1614+ errdefer {
1615+ for (not_currently_loading_uris .items ) | duped_import_uri | {
1616+ store .allocator .free (duped_import_uri );
1617+ }
1618+ }
1619+
1620+ {
1621+ store .lock .lockShared ();
1622+ defer store .lock .unlockShared ();
1623+
1624+ for (import_uris ) | import_uri | {
1625+ if (! store .handles .contains (import_uri ) and
1626+ ! store .currently_loading_uris .contains (import_uri ))
1627+ {
1628+ not_currently_loading_uris .appendAssumeCapacity (
1629+ try store .allocator .dupe (u8 , import_uri ),
1630+ );
1631+ }
1632+ }
1633+ }
1634+
1635+ errdefer comptime unreachable ;
1636+
1637+ const S = struct {
1638+ fn getOrLoadHandleVoid (s : * DocumentStore , duped_import_uri : Uri ) void {
1639+ _ = s .getOrLoadHandle (duped_import_uri );
1640+ defer s .allocator .free (duped_import_uri );
1641+ }
1642+ };
1643+
1644+ for (not_currently_loading_uris .items ) | duped_import_uri | {
1645+ store .thread_pool .spawn (S .getOrLoadHandleVoid , .{ store , duped_import_uri }) catch {
1646+ defer store .allocator .free (duped_import_uri );
1647+ _ = store .getOrLoadHandle (duped_import_uri );
1648+ };
1649+ }
1650+ }
1651+
15711652/// Caller owns returned memory.
15721653/// **Thread safe** takes a shared lock
15731654fn collectImportUris (self : * DocumentStore , handle : * Handle ) error {OutOfMemory }! std .ArrayListUnmanaged (Uri ) {
0 commit comments