-
Notifications
You must be signed in to change notification settings - Fork 104
fix: prevent IdleTimeout from closing control connection during activ… #577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…e data transfers Before this change, the server would immediately close control connections when IdleTimeout was reached, regardless of whether a data transfer was active. This caused issues with large or slow file transfers that could take longer than the configured idle timeout. Now the server checks if there is an active data transfer (isTransferOpen) before closing the connection. If a transfer is active, it extends the deadline instead of closing the connection, allowing the transfer to complete. Fixes #430 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Florent Clairambault <[email protected]>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #577 +/- ##
==========================================
- Coverage 86.74% 86.40% -0.35%
==========================================
Files 12 12
Lines 2369 2383 +14
==========================================
+ Hits 2055 2059 +4
- Misses 238 247 +9
- Partials 76 77 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code Review for PR #577SummaryThis PR addresses issue #430 by preventing the control connection from being closed due to idle timeout when there's an active data transfer in progress. The solution is straightforward and correctly identifies active transfers before deciding whether to close the connection. ✅ Strengths
🔍 Issues and Recommendations1. Potential Infinite Loop Risk 🚨The current implementation extends the deadline by IdleTimeout seconds each time. If a transfer stalls (e.g., network issues, client hanging), this could keep the connection alive indefinitely. Recommendation: Consider adding a maximum transfer timeout or counter to prevent infinite extensions: // Add a field to track consecutive extensions
if hasActiveTransfer {
if c.transferTimeoutExtensions >= maxTransferExtensions {
// Force close after too many extensions
c.logger.Warn("Transfer timeout exceeded maximum extensions")
// Continue with normal timeout handling
} else {
c.transferTimeoutExtensions++
// Extend deadline...
}
}2. Missing Test Coverage
|
Code Review for PR #577: Fix IdleTimeout during active transfersThank you for this fix! I've reviewed the changes and have the following feedback: ✅ Strengths
🔍 Areas for Improvement1. Deadline Extension LogicThe current implementation has a potential issue with the deadline extension calculation: extendedDeadline := time.Now().Add(time.Duration(time.Second.Nanoseconds() * int64(c.server.settings.IdleTimeout)))Issues:
2. Missing Test CoverageThe PR doesn't include tests for this new behavior. Consider adding a test case that:
Example test structure: func TestIdleTimeoutWithActiveTransfer(t *testing.T) {
// Create server with 2-second idle timeout
// Start a file transfer
// Sleep for 3 seconds (> idle timeout)
// Verify connection is still alive
// Complete transfer
// Wait for idle timeout
// Verify connection closes
}3. Potential Race ConditionWhile the mutex protects 4. Consider Alternative ApproachesInstead of repeatedly extending on timeout, consider:
📝 Suggested ImplementationHere's a more robust approach: if hasActiveTransfer {
// Extend by a reasonable period (e.g., 5 minutes or 10x idle timeout)
extensionPeriod := time.Duration(c.server.settings.IdleTimeout) * time.Second * 10
if extensionPeriod < 5*time.Minute {
extensionPeriod = 5 * time.Minute
}
if errSet := c.conn.SetDeadline(time.Now().Add(extensionPeriod)); errSet != nil {
c.logger.Error("Could not extend deadline during transfer", "err", errSet)
// Consider whether to continue or close the connection here
}
if c.debug {
c.logger.Debug("Extended deadline due to active transfer",
"extension", extensionPeriod)
}
return
}🚀 Overall AssessmentThis is a good fix for an important issue. With the suggested improvements to the deadline calculation and added test coverage, this will be a solid addition to the codebase. The fix aligns well with the library's architecture and maintains backward compatibility. |
Code Review for PR #577I've reviewed the changes to fix the IdleTimeout issue during active data transfers. Here's my feedback: ✅ Positive Aspects
|
Pull Request Review: Fix IdleTimeout During Active TransfersThank you for addressing issue #430! This is an important fix for handling large or slow file transfers. Positive Aspects
Issues and Concerns1. Logic Flow Problem (client_handler.go:479-493)After the new code block that extends the deadline and returns early, the original timeout handling code still executes when there's NO active transfer. This code path extends the deadline by 1 minute (line 480-481) but then immediately logs timeout and notifies the client (line 484-491). Why extend the deadline only to immediately close the connection? This seems like a legacy quirk - the 1-minute extension appears necessary only to allow the error message to be sent. 2. Inconsistent Deadline CalculationThe new code uses: time.Duration(time.Second.Nanoseconds() * int64(c.server.settings.IdleTimeout)) Recommendation: Consider extracting a helper method to make this consistent and clearer. 3. Potential Race Condition WindowThere's a brief window between checking isTransferOpen and extending the deadline where the transfer could complete. While unlikely to cause issues in practice, the worst case is an extra deadline extension which is benign. 4. Missing Test CoverageThe existing TestIdleTimeout (handle_misc_test.go:44) doesn't cover the new behavior. A test case should verify that the control connection remains open during active data transfers that exceed IdleTimeout. Suggested Improvements
Security ConsiderationsPotential DoS Vector: A malicious client could keep a data connection open indefinitely to bypass idle timeout. However, this is mitigated by the ConnectionTimeout setting for data connections, and existing behavior already allows this via legitimate slow transfers. No new security concerns are introduced. VerdictConditionally Approve - The fix correctly addresses the reported issue and is safe to merge for fixing the immediate problem. The suggested improvements would enhance maintainability and test coverage. Review generated with assistance from Claude Code |
Pull Request Review: Fix IdleTimeout During Active TransfersSummaryThis PR addresses issue #430 by preventing the IdleTimeout from closing the control connection when a data transfer is active. The approach is sound and directly solves the reported problem. Code Quality & Best Practices✅ Strengths:
Potential Bugs & Issues🔴 Critical: Infinite Loop Risk The current implementation has a significant flaw: when a transfer is active, it extends the deadline and returns immediately, but the main command loop (client_handler.go:440) will immediately set the deadline again before the next Scenario:
This creates a busy loop that repeatedly wakes up every Recommended Fix: // Give more time for transfers - extend by IdleTimeout rather than setting to IdleTimeout
extendedDeadline := time.Now().Add(time.Duration(c.server.settings.IdleTimeout) * time.Second * 2)Or better yet, extend by a multiple of IdleTimeout or use a maximum transfer timeout setting. Race Conditions✅ Thread Safety:
Impact: Low - worst case is one extra IdleTimeout extension after transfer completes. Not critical, but worth noting. Mitigation (if needed): Hold the lock while setting the deadline, though this adds minimal value given the low impact. Performance Considerations
Security ConcernsAn attacker could potentially keep a connection alive indefinitely by:
Recommendation: type Settings struct {
IdleTimeout int // Control connection idle timeout
MaxTransferDuration int // Maximum time for a single transfer (0 = unlimited)
}This would allow long transfers while preventing infinite connections. Test Coverage❌ Missing Tests: The PR does not include any tests. Given that:
Required: func TestIdleTimeoutDuringTransfer(t *testing.T) {
// Setup server with short IdleTimeout
// Start a file transfer
// Verify control connection stays alive during transfer
// Verify control connection times out after transfer completes + IdleTimeout
}This is critical for preventing regression. Recommended ChangesHigh Priority:
Medium Priority: Low Priority: Overall AssessmentVerdict: The PR correctly identifies and addresses the root cause, but the implementation needs refinement to avoid creating a busy loop during transfers. With the suggested fixes and test coverage, this will be a solid improvement. Estimated Risk: Medium (without fixes), Low (with fixes) 🤖 Generated with Claude Code |
Pull Request ReviewThank you for addressing issue #430! This is an important fix for handling idle timeouts during active data transfers. Here's my comprehensive review: ✅ Code Quality & Best PracticesStrengths:
Minor style notes:
🐛 Potential Issues & Concerns1. Race Condition Risk (Medium Priority)The current implementation has a potential check-then-act race condition: c.transferMu.Lock()
hasActiveTransfer := c.isTransferOpen
c.transferMu.Unlock()
if hasActiveTransfer {
// Between unlock and here, transfer could close
extendedDeadline := time.Now().Add(...)
c.conn.SetDeadline(extendedDeadline)
}Issue: After unlocking Recommendation: Hold the lock during the c.transferMu.Lock()
if c.isTransferOpen {
extendedDeadline := time.Now().Add(...)
if errSet := c.conn.SetDeadline(extendedDeadline); errSet != nil {
c.logger.Error("Could not extend read deadline during active transfer", "err", errSet)
}
if c.debug {
c.logger.Debug("Idle timeout occurred during active transfer, extending deadline")
}
c.transferMu.Unlock()
return
}
c.transferMu.Unlock()However, since 2. Deadline Calculation Duplication (Low Priority)The deadline calculation is duplicated:
Recommendation: Extract to a helper method or reuse the same calculation pattern. This improves maintainability. 3. Incomplete Fix - Original Timeout Logic Still Executes (High Priority)CRITICAL ISSUE: After the new code returns early, the original timeout handling code at lines 479-493 still executes in the non-transfer case. However, looking at the diff, the new code is inserted before the existing timeout handling. This means:
Actually, this is correct! The early ⚡ Performance Considerations
🔒 Security ConcernsPotential DoS vector (Low-Medium Risk):
Recommendation: Document this behavior and ensure transfer connections have appropriate timeouts configured elsewhere in the codebase. This is likely already handled but worth verifying. 🧪 Test CoverageCurrent state:
Recommendation: Add a test case that:
Example test outline: func TestIdleTimeoutDuringActiveTransfer(t *testing.T) {
server := NewTestServerWithTestDriver(t, &TestServerDriver{
Debug: false,
Settings: &Settings{IdleTimeout: 2},
})
// 1. Connect and authenticate
// 2. Start a STOR/RETR that takes longer than IdleTimeout
// 3. Wait IdleTimeout + 1 second
// 4. Verify control connection is still alive (send NOOP)
// 5. Complete transfer
// 6. Wait IdleTimeout + 1 second
// 7. Verify control connection now times out
}📋 SummaryVerdict: ✅ Approve with recommendations This PR correctly addresses the core issue from #430. The fix is conceptually sound and follows the codebase conventions well. Required before merge:
Optional improvements: Great work on identifying and fixing this issue! The comments in the existing code ( Review generated with analysis of code patterns, concurrency safety, and FTP protocol requirements |
…e data transfers
Before this change, the server would immediately close control connections when IdleTimeout was reached, regardless of whether a data transfer was active. This caused issues with large or slow file transfers that could take longer than the configured idle timeout.
Now the server checks if there is an active data transfer (isTransferOpen) before closing the connection. If a transfer is active, it extends the deadline instead of closing the connection, allowing the transfer to complete.
Fixes #430
🤖 Generated with Claude Code