Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any circumstances in which an IF statement exits early if the triggering condition fails after the if statement is triggered?

Tags:

c++

Question: For a conditional statement that contains code that invalidates the condition that triggered it, are there any conditions in which the conditional will exit, prematurely, without a break function? Similar to this:

IF(x == 0){x=1;}

Background I am working with a sizable c++ program that consists of a commercial program which acts like a wrapper. This wrapper handles etherCAT communication and interface with the computer link layer and my program exists inside. My program exists of 3 threads (other threads exists in this commercial wrapper, but I have limited info on them):

  • MyAppPD: Commercial function, retrieves EtherCAT PD information from memory and passes it to a global class. This thread operates synchronous with the EtherCAT cycle and I have little control over it's memory access.
  • Conversions: This thread receives data from the global class, processes it, and returns the output to MyAppPD via the global class. This thread operates synchronous with MyAppPD.
  • Calculations: This thread operates asynchronous to the other threads, receiving data via the global class as it is made available within Conversions. It's output is returned to Conversions via the same global class.
  • Global class: The global class that I keep referring to is an object that passes data back and forth between two threads through the use of pthread mutex locks. A lock is used for each data path (Conversion -> Calculations and Calculations -> Conversions consist of two locks), with private class variables used as temporary storage, so that data from one thread is never directly accessible by another without a mutex lock being applied first.

In depth problem: Recently I added a new level to this global class to facilitate the transfer of trajectory data from Calculations to Conversions. It consists of a structure of the following form:

struct DCommand {
    struct DOptions {
        bool CSPI = false;
        bool Blend = false;
        bool Cyclic = false;
        bool StopM = false;
    };
    DOptions Opt;
    struct Drive {
        int INC_max;
        bool Init_Accel = false;
        bool Init_MTorq = false;
        std::vector<int32_t> Th = std::vector<int32_t>(200);
        std::vector<uint32_t> ThDot = std::vector<uint32_t>(200);
        std::vector<uint32_t> ThDotDot = std::vector<uint32_t>(200);
        std::vector<uint16_t> MTorq = std::vector<uint16_t>(200);
    };
    Drive D1;
    Drive D2;
};

This struct is passed from the Calculations thread to the global class (through a mutex lock) where it is copied to a private global struct, this action sets a boolean. The Conversion thread queries the global class every cycle to check if new data is available, if the boolean is true, it initiates a query. The query function from the global class to Conversions is as follows:

DCommand PDSync::Sync2Conv(){
    DCommand Out_DCom; //Create temporary struct

    pthread_mutex_lock(&Calc_lock_mutex);
    
    Out_DCom = DrvCom; //copy intermediate struct
    
    pthread_mutex_unlock(&Calc_lock_mutex);
    
    Conv_shouldread = false; //If true, Conv_ReadAvail returns true

    return Out_DCom;
} 

The requesting function from Conversions is as follows:

if(ThreadSync.Conv_ReadAvail()){ 
    ConvDrv = ThreadSync.Sync2Conv();

    TS.SetEE_LoadStruct(ConvDrv); //Copy struct from threadsync to local trajectory 
}

Where ThreadSync is the global class object. Now, this code malfunctions for some reason. I can trace the issue to just before the "return" statement in "Sync2Conv", where a simple cout tracer functions ok, it acts as tho the statment "ConvDrv = ThreadSync.Sync2Conv();" does not complete. Even more curious is that the program after the statement "if(ThreadSync.Conv_ReadAvail())" conditional continues as if it had encountered a simple break; in the if statement. Even more curious is that once I CTRL+C out of the program (setting the shutdown on the commercial wrapper and terminating the ethercat connection), the statements Sync2Conv and SetEE_LoadStruct complete just before shutdown.

Attempts to solve the issue:

  • Renamed the Conversions thread struct that is receiving the data from the global class, in case it happens to have a conflicting identifier with some function in the commercial wrapper.
  • Checked the mutex locks for a deadlock condition. None exist.
  • Checked that only the appropriate thread is trying to access the Sync2Conv function via pthread_self(). No other threads access that function during the lifetime of the program.
  • Sequentially deactivated the function, line by line, which leads me to the only resolution I have found so far:

Partial solutions: Deactivating the trigger that prevents repeated reads of the Sync2Conv function:

Conv_shouldread = false; //If true, Conv_ReadAvail returns true

will allow the program to progress correctly, but then the function is accessed every cycle instead of just when new data is available. It kind of looks like when the variable that allows the IF conditional statement switches state, the IF conditional exits (or hangs somehow). Can anyone offer advice on this?

Full Solution The race condition seemed to arise within the trigger condition for the conditional IF statement shown above. The boolean, Conv_shouldread, is used within the trigger condition to determine when to try and access data in the global.

Extending the same mutex lock around the boolean access point, so that the state of that boolean, in the context of the trigger condition, is known before it can be accessed anywhere else seems to have corrected the issue. See below:

Before:

bool PDSync::Conv_ReadAvail(){
    if(Calc_isavailable && Conv_shouldread){
        return true;
    }else{
        return false;
    }
}

After:

bool PDSync::Conv_ReadAvail(){
    bool temp = false;
    pthread_mutex_lock(&Calc_lock_mutex);
        if(Conv_shouldread){
            temp = true;
        }else{temp = false;}
    pthread_mutex_unlock(&Calc_lock_mutex);

    if(Calc_isavailable && temp){
        return true;
    }else{
        return false;
    }
}

The read reset boolean, Conv_shouldread = false, was also moved back inside the mutex guards so that it cannot be accessed at the same time as the IF conditional trigger statement.

It should be noted that I have similar code in 3 other locations in the global class that have never experienced problematic race conditions, but all of those transfer much smaller amounts of data, so I think that copy time may be a factor here. Either way, I am going to correct them as this one was done to hopefully avoid future issues. Thank you, everyone, for the help!

like image 688
JDD Avatar asked Apr 14 '21 04:04

JDD


People also ask

Can you break out of an if statement?

You can't break out of if statement until the if is inside a loop. The behaviour of the break statement is well specified and, generally, well understood. An inexperienced coder may cause crashes though a lack of understanding in many ways. Misuse of the break statement isn't special.

What will happen if the first condition fails in a Boolean expression with and operator?

No, if the first condition returns false then the whole expression automatically returns false. Java will not bother examining the other condition.

Does if statement check all conditions?

The starting point for handling conditions is a single if statement, which checks if a condition is true. If so, the indented block of code directly under the if statement is executed. The condition must evaluate either True or False .

How many times does an if statement check a condition?

Technically only 1 condition is evaluated inside an if , what is ascually happening is 1 condition gets evaluated and its result is evaluated with the next and so on... There is no defined limit to the length of a boolean expression; likely, you will run into memory problems at some point.


1 Answers

The answer to your question is: "No, only in a multi-threaded context."

In a multi-threaded context almost anything you cannot imagine will happen. The background and context you provided is extensive but not sufficient to study this.

One thing Conv_shouldread = false; //If true, Conv_ReadAvail returns true apparently modifies (or determines) the outcome of if(ThreadSync.Conv_ReadAvail()) and it seem obvious that other threads are also interfering with this. I would, thus, clearly put the Conv_shouldread = false; inside the mutex protection.

But besides this, I must confess the problem could be clearly somewhere else and there can be a lot of side effects between different threads.

like image 60
Ralf Ulrich Avatar answered Oct 21 '22 21:10

Ralf Ulrich