Skip to content

Commit 86fabec

Browse files
authored
Merge pull request #26 from pterodactyl-china/copilot/fix-extraction-performance-issue
Add timeout to space check to fix slow decompression while preserving safety
2 parents 0b30e08 + 8c035e7 commit 86fabec

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

router/router_server_files.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,20 +447,38 @@ func postServerDecompressFiles(c *gin.Context) {
447447

448448
s := middleware.ExtractServer(c)
449449
lg := middleware.ExtractLogger(c).WithFields(log.Fields{"root_path": data.RootPath, "file": data.File})
450-
lg.Debug("checking if space is available for file decompression")
451-
err := s.Filesystem().SpaceAvailableForDecompression(context.Background(), data.RootPath, data.File)
450+
451+
// Check if there's enough space for decompression. This uses a 5-second timeout
452+
// to avoid delays on large archives - if it times out, decompression proceeds
453+
// with incremental space checking during extraction.
454+
err := s.Filesystem().SpaceAvailableForDecompression(c.Request.Context(), data.RootPath, data.File)
452455
if err != nil {
453456
if filesystem.IsErrorCode(err, filesystem.ErrCodeUnknownArchive) {
454457
lg.WithField("error", err).Warn("failed to decompress file: unknown archive format")
455458
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "The archive provided is in a format Wings does not understand."})
456459
return
457460
}
461+
if filesystem.IsErrorCode(err, filesystem.ErrCodeDiskSpace) {
462+
lg.WithField("error", err).Warn("failed to decompress file: not enough disk space")
463+
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "There is not enough disk space available to decompress this archive."})
464+
return
465+
}
458466
middleware.CaptureAndAbort(c, err)
459467
return
460468
}
461469

462470
lg.Info("starting file decompression")
463-
if err := s.Filesystem().DecompressFile(context.Background(), data.RootPath, data.File); err != nil {
471+
if err := s.Filesystem().DecompressFile(c.Request.Context(), data.RootPath, data.File); err != nil {
472+
if filesystem.IsErrorCode(err, filesystem.ErrCodeUnknownArchive) {
473+
lg.WithField("error", err).Warn("failed to decompress file: unknown archive format")
474+
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "The archive provided is in a format Wings does not understand."})
475+
return
476+
}
477+
if filesystem.IsErrorCode(err, filesystem.ErrCodeDiskSpace) {
478+
lg.WithField("error", err).Warn("failed to decompress file: not enough disk space")
479+
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "There is not enough disk space available to decompress this archive."})
480+
return
481+
}
464482
// If the file is busy for some reason just return a nicer error to the user since there is not
465483
// much we specifically can do. They'll need to stop the running server process in order to overwrite
466484
// a file like this.

server/filesystem/compress.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ func (fs *Filesystem) archiverFileSystem(ctx context.Context, p string) (iofs.FS
9898

9999
// SpaceAvailableForDecompression looks through a given archive and determines
100100
// if decompressing it would put the server over its allocated disk space limit.
101+
// To avoid long delays on large archives, this function will timeout after 5 seconds
102+
// and allow decompression to proceed (space will still be checked incrementally during extraction).
101103
func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir string, file string) error {
102104
// Don't waste time trying to determine this if we know the server will have the space for
103105
// it since there is no limit.
@@ -113,16 +115,22 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
113115
return err
114116
}
115117

118+
// Create a context with timeout to prevent long delays on large archives
119+
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
120+
defer cancel()
121+
116122
var size atomic.Int64
117-
return iofs.WalkDir(fsys, ".", func(path string, d iofs.DirEntry, err error) error {
123+
err = iofs.WalkDir(fsys, ".", func(path string, d iofs.DirEntry, err error) error {
118124
if err != nil {
119125
return err
120126
}
121127

122128
select {
123-
case <-ctx.Done():
124-
// Stop walking if the context is canceled.
125-
return ctx.Err()
129+
case <-timeoutCtx.Done():
130+
// Stop walking if the timeout is reached or context is canceled.
131+
// We'll check below whether to ignore the error (for timeouts)
132+
// or propagate it (for cancellations).
133+
return timeoutCtx.Err()
126134
default:
127135
info, err := d.Info()
128136
if err != nil {
@@ -134,6 +142,14 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
134142
return nil
135143
}
136144
})
145+
146+
// If the error is a timeout, ignore it and allow decompression to proceed.
147+
// Space will still be checked incrementally during the actual extraction.
148+
if errors.Is(err, context.DeadlineExceeded) {
149+
return nil
150+
}
151+
152+
return err
137153
}
138154

139155
// DecompressFile will decompress a file in a given directory by using the

0 commit comments

Comments
 (0)