@@ -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,10 +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 ()
116113
117114 if err := os .MkdirAll (s .RootDir , 0700 ); err != nil {
118115 return "" , err
@@ -330,6 +327,9 @@ func (s *LocalDirV1) NewObjectLoader(catalog string) (gql.ObjectLoader, error) {
330327 catalogPath := catalogFilePath (s .catalogDir (catalog ))
331328
332329 return func (schemaName string , offset , limit int ) ([]map [string ]interface {}, error ) {
330+ s .m .RLock ()
331+ defer s .m .RUnlock ()
332+
333333 sections := idx .GetSchemaSections (schemaName )
334334 if sections == nil {
335335 return nil , nil
@@ -350,8 +350,12 @@ func (s *LocalDirV1) NewObjectLoader(catalog string) (gql.ObjectLoader, error) {
350350 }
351351 defer f .Close ()
352352
353+ const maxEntrySize = 16 * 1024 * 1024 // 16 MiB
353354 results := make ([]map [string ]interface {}, 0 , len (sections ))
354355 for _ , sec := range sections {
356+ if sec .Length <= 0 || sec .Length > maxEntrySize {
357+ return nil , fmt .Errorf ("invalid section length %d at offset %d" , sec .Length , sec .Offset )
358+ }
355359 buf := make ([]byte , sec .Length )
356360 if _ , err := f .ReadAt (buf , sec .Offset ); err != nil {
357361 return nil , fmt .Errorf ("error reading section at offset %d: %w" , sec .Offset , err )
0 commit comments