@@ -81,20 +81,19 @@ func NewLocalDirV1(rootDir string, rootURL *url.URL, enableMetasHandler MetasHan
8181}
8282
8383func (s * LocalDirV1 ) Store (ctx context.Context , catalog string , fsys fs.FS ) error {
84+ s .m .Lock ()
8485 catalogDir , err := s .storeAtomicSwap (ctx , catalog , fsys )
8586 if err != nil {
87+ s .m .Unlock ()
8688 return err
8789 }
90+ s .m .Unlock ()
8891
89- // Pre-warm GraphQL schema cache outside the write lock.
90- // Concurrent queries during this window get a cache miss and trigger
91- // their own build via singleflight, which is safe — the data is on disk.
9292 if s .graphqlSvc != nil {
9393 s .graphqlSvc .InvalidateCache (catalog )
9494
9595 if _ , err := s .graphqlSvc .GetSchema (ctx , catalog ); err != nil {
96- // Schema build failed — remove the catalog to maintain consistency.
97- // Re-acquire the write lock for the rollback since it touches shared filesystem state.
96+ s .graphqlSvc .InvalidateCache (catalog )
9897 s .m .Lock ()
9998 removeErr := os .RemoveAll (catalogDir )
10099 s .m .Unlock ()
@@ -109,11 +108,8 @@ func (s *LocalDirV1) Store(ctx context.Context, catalog string, fsys fs.FS) erro
109108}
110109
111110// storeAtomicSwap writes catalog data to a temp dir and atomically swaps it
112- // into place. Holds the write lock for the duration of the filesystem operations only .
111+ // into place. Caller must hold s.m write lock .
113112func (s * LocalDirV1 ) storeAtomicSwap (ctx context.Context , catalog string , fsys fs.FS ) (string , error ) {
114- s .m .Lock ()
115- defer s .m .Unlock ()
116-
117113 if err := os .MkdirAll (s .RootDir , 0700 ); err != nil {
118114 return "" , err
119115 }
@@ -330,6 +326,9 @@ func (s *LocalDirV1) NewObjectLoader(catalog string) (gql.ObjectLoader, error) {
330326 catalogPath := catalogFilePath (s .catalogDir (catalog ))
331327
332328 return func (schemaName string , offset , limit int ) ([]map [string ]interface {}, error ) {
329+ s .m .RLock ()
330+ defer s .m .RUnlock ()
331+
333332 sections := idx .GetSchemaSections (schemaName )
334333 if sections == nil {
335334 return nil , nil
@@ -350,8 +349,12 @@ func (s *LocalDirV1) NewObjectLoader(catalog string) (gql.ObjectLoader, error) {
350349 }
351350 defer f .Close ()
352351
352+ const maxEntrySize = 16 * 1024 * 1024 // 16 MiB
353353 results := make ([]map [string ]interface {}, 0 , len (sections ))
354354 for _ , sec := range sections {
355+ if sec .Length <= 0 || sec .Length > maxEntrySize {
356+ return nil , fmt .Errorf ("invalid section length %d at offset %d" , sec .Length , sec .Offset )
357+ }
355358 buf := make ([]byte , sec .Length )
356359 if _ , err := f .ReadAt (buf , sec .Offset ); err != nil {
357360 return nil , fmt .Errorf ("error reading section at offset %d: %w" , sec .Offset , err )
0 commit comments