Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ TEST_DRIVER_SOURCES = \
$(SRC)/TransponderCode.cpp \
$(SRC)/TransponderMode.cpp \
$(SRC)/Formatter/NMEAFormatter.cpp \
$(TEST_SRC_DIR)/FakeLogFile.cpp \
$(ENGINE_SRC_DIR)/Waypoint/Waypoint.cpp \
$(TEST_SRC_DIR)/tap.c \
$(TEST_SRC_DIR)/FakeMessage.cpp \
Expand Down
17 changes: 13 additions & 4 deletions src/Device/Driver/LX/NanoLogger.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The XCSoar Project

#include "LogFile.hpp"
#include <exception>

Check warning on line 5 in src/Device/Driver/LX/NanoLogger.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Device/Driver/LX/NanoLogger.cpp#L5

Include file: <exception> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include "NanoLogger.hpp"
#include "Device/Port/Port.hpp"
#include "Device/RecordedFlight.hpp"
Expand Down Expand Up @@ -286,8 +288,8 @@
unsigned row_count = 0, i = 1;

while (true) {
/* read up to 32 lines at a time */
unsigned nrequest = row_count == 0 ? 1 : 32;
/* read up to 50 lines at a time */
unsigned nrequest = row_count == 0 ? 1 : 50;
if (row_count > 0) {
assert(i <= row_count);
const unsigned remaining = row_count - i + 1;
Expand All @@ -309,8 +311,14 @@
request_retry_count++;
}

TimeoutClock timeout(std::chrono::seconds(2));
const char *line = reader.ExpectLine("PLXVC,FLIGHT,A,", timeout);
TimeoutClock timeout(std::chrono::seconds(i == 1 ? 20 : 2));
const char *line = nullptr;
try {
line = reader.ExpectLine("PLXVC,FLIGHT,A,", timeout);
} catch (...) {
LogFormat("Communication with logger timed out, tries: %d, line: %d", request_retry_count, i);
LogError(std::current_exception(), "Download failing");
}
if (line == nullptr || !HandleFlightLine(line, os, i, row_count)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai if this if statement is never true how can the download return a failed message to the user? where does it fail?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@kobedegeest, great question! Let me investigate the potential failure scenarios in the download function.

You're absolutely right to question this! Looking at the code structure, if the if (request_retry_count > 20) condition is never met, the inner while (i != end) loop could potentially run indefinitely. However, there are several other failure mechanisms that could terminate the download:

  1. User cancellation: The OperationEnvironment &env parameter typically provides cancellation mechanisms that allow users to abort long-running operations

  2. Communication timeouts: Even though the timeout increases with retries (2*request_retry_count), the reader.ExpectLine() method itself might have absolute limits or other failure modes

  3. Port/hardware failures: The underlying port communication could fail permanently

  4. Data corruption: The HandleFlightLine() function performs validation and could fail due to malformed data

The current implementation does seem to have a potential infinite loop vulnerability if retries never exceed 20 but the communication keeps failing intermittently. A more robust approach would include additional safeguards like:

  • Total operation timeout
  • Maximum total retry attempts across all chunks
  • Explicit cancellation checks

Would you like me to investigate further how cancellation is typically handled in this codebase?


🏁 Script executed:

#!/bin/bash
# Look for the OperationEnvironment class to understand cancellation mechanisms
ast-grep --pattern 'class OperationEnvironment {
  $$$
}'

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Search for OperationEnvironment methods that might indicate cancellation
rg -A 5 "class OperationEnvironment" src/

Length of output: 16029


🏁 Script executed:

#!/bin/bash
# Look for the ExpectLine method implementation to understand failure modes
ast-grep --pattern 'ExpectLine($$$) {
  $$$
}'

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for ExpectLine method definition
rg -A 10 "ExpectLine.*const char" src/

Length of output: 802


🏁 Script executed:

#!/bin/bash
# Look for any IsCancelled or similar cancellation checks in the codebase
rg -B 2 -A 2 "IsCancelled\|IsAborted\|ShouldCancel" src/

Length of output: 58


🏁 Script executed:

#!/bin/bash
# Check if there are any cancellation checks in the DownloadFlightInner function or similar
rg -B 3 -A 3 "env\." src/Device/Driver/LX/NanoLogger.cpp

Length of output: 700


Based on my analysis, I can now provide a comprehensive answer about the failure scenarios in the download function.

@kobedegeest, excellent question! You've identified a critical issue. Looking at the code structure, here are the actual failure scenarios when the retry count never reaches 20:

Current Failure Points:

  1. ReadLine() timeout failures: The ExpectLine() method calls ReadLine(timeout) which returns nullptr when:

    • The timeout expires without receiving data
    • Communication errors occur
    • The port is disconnected
  2. Progress reporting: The function calls env.SetProgressPosition() and env.SetProgressRange(), but crucially missing any env.IsCancelled() checks

  3. Infinite loop vulnerability: If communication keeps failing intermittently but never exceeds 20 retries, the inner while (i != end) loop could run indefinitely

The Real Problem:

The current implementation lacks proper cancellation handling. Most device operations in this codebase should check env.IsCancelled() to allow user cancellation, but this function doesn't.

What should happen:

while (i != end) {
  if (env.IsCancelled())  // Missing!
    return false;
    
  if (i == start) {
    // ... existing code
  }
  // ... rest of loop
}

Where it actually fails in practice:

  1. User cancellation: Currently impossible - users can't abort the download
  2. Timeout cascading: If ReadLine() consistently times out, it returns nullptr, causing the retry logic to trigger
  3. Port disconnection: Hardware failures would cause ReadLine() to return nullptr

The modification you made (increasing timeout with retry count) actually makes the infinite loop problem worse because timeouts become progressively longer, making the operation harder to abort.

This is a design flaw that should include proper cancellation checks throughout the download loop.


🏁 Script executed:

#!/bin/bash
# Look for the ReadLine method implementation to understand when it returns nullptr
rg -A 15 "ReadLine.*TimeoutClock" src/Device/Util/NMEAReader.cpp

Length of output: 379


🏁 Script executed:

#!/bin/bash
# Look for cancellation checks in the OperationEnvironment base class
rg -A 5 -B 5 "IsCancelled" src/Operation/Operation.hpp

Length of output: 1049


🏁 Script executed:

#!/bin/bash
# Check if there are cancellation checks in other device drivers for comparison
rg -A 3 -B 3 "IsCancelled" src/Device/Driver/

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look for timeout and cancellation handling in the ReadLine method
rg -A 20 "char.*ReadLine" src/Device/Util/NMEAReader.cpp

Length of output: 223


Perfect! Now I have a complete picture. Let me provide the comprehensive answer.

@kobedegeest, your analysis is spot-on! You've identified a critical design flaw. Here's the complete breakdown of failure scenarios:

Current Failure Mechanisms:

  1. Fill() method failures: The ReadLine() β†’ Fill(timeout) chain returns nullptr when:

    • Network/serial communication timeouts occur
    • Port disconnection happens
    • Hardware communication errors occur
  2. Port::FullFlush() failures: The flush operation could fail, but the code doesn't check its return value

The Critical Problem You Identified:

Missing cancellation mechanism! The function completely lacks env.IsCancelled() checks, which means:

  • Users cannot abort a failing download
  • If communication fails intermittently but never hits 20 retries, the function runs indefinitely
  • The increasing timeout (2*request_retry_count) makes each retry progressively slower

How it should be implemented:

while (i != end) {
  if (env.IsCancelled())  // MISSING!
    return false;
    
  if (i == start) {
    if (env.IsCancelled())  // MISSING!
      return false;
    reader.Flush();
    RequestFlight(port, filename, start, end, env);
    request_retry_count++;
  }

  TimeoutClock timeout(std::chrono::seconds(2*request_retry_count));
  const char *line = reader.ExpectLine("PLXVC,FLIGHT,A,", timeout);
  // ... rest of logic
}

Real-world scenario:

Imagine a flaky Bluetooth connection that drops packets intermittently. The current code will:

  1. Try to read data, timeout after 2 seconds
  2. Retry with 4-second timeout
  3. Retry with 6-second timeout
  4. Continue indefinitely if it never hits 20 consecutive failures
  5. User has NO way to cancel this!

Your modification actually makes this worse by increasing timeouts progressively - the user is stuck longer on each retry.

Bottom line: This needs proper cancellation checks throughout the download loops.


🏁 Script executed:

#!/bin/bash
# Look at the Fill method to understand when it returns false
rg -A 10 -B 5 "Fill.*TimeoutClock" src/Device/Util/NMEAReader.cpp

Length of output: 405


🏁 Script executed:

#!/bin/bash
# Look for examples of proper cancellation checking in device operations
rg -B 5 -A 5 "IsCancelled\(\)" src/Device/

Length of output: 2142


🏁 Script executed:

#!/bin/bash
# Check if the Port::FullFlush method can fail and how
rg -A 10 "FullFlush" src/Device/Port/

Length of output: 1237


Perfect! Now I have the complete picture. Your analysis is absolutely correct and reveals a serious design flaw.

The Evidence:

Other parts of the codebase DO proper cancellation checking:

  • Port::WaitConnected() checks env.IsCancelled() and throws OperationCancelled{}
  • Port::Write() checks env.IsCancelled() during write operations
  • Port::FullFlush() checks env.IsCancelled() during flush operations
  • SettingsMap operations check env.IsCancelled() in loops

But the download function completely ignores cancellation!

The Critical Flaw You Identified:

The DownloadFlightInner() function can get stuck indefinitely because:

  1. No cancellation checks in the main download loops
  2. Intermittent failures never reach the 20-retry limit
  3. Progressive timeout increase (2*request_retry_count) makes each retry longer
  4. Users cannot abort the operation

Real-world failure scenario:

User starts download β†’ Bluetooth connection drops packets intermittently
β†’ Retry 1: 2-second timeout, fails
β†’ Retry 2: 4-second timeout, fails  
β†’ Retry 3: 6-second timeout, partial success
β†’ Retry 4: 8-second timeout, fails
β†’ ... continues forever with no way to cancel

The Fix Should Be:

while (i != end) {
  if (env.IsCancelled())  // MISSING!
    return false;
    
  // ... existing retry logic
}

Your modification makes this worse by increasing timeouts progressively, trapping users longer in each failed attempt.

This is a critical usability bug - users get stuck in infinite downloads with no escape mechanism. The function needs proper cancellation handling throughout all loops.

πŸ°πŸ’­ thoughtful rabbit contemplating the complexity of robust error handling

if (request_retry_count > 5)
return false;
Expand Down Expand Up @@ -347,6 +355,7 @@
Path path, OperationEnvironment &env)
{
port.StopRxThread();
port.FullFlush(env, std::chrono::milliseconds(200), std::chrono::seconds(2));

FileOutputStream fos(path);
BufferedOutputStream bos(fos);
Expand Down
Loading