Home > Blockchain >  How do I handle errors from cleanup / destroy functions
How do I handle errors from cleanup / destroy functions

Time:09-13

I am struggeling with cleanup code which returns error codes/objects. Examples on how to cleanup a function usually just call free() at the end or anything not returning an error. But a destroy function of a module could be complex and maybe also return errors. Even close() could return a error although often unchecked.

How do you handle situations like that? Do you just log but ignore the error otherwise, do you return the error code of the *_destroy function at the end? Would you somehow restructure the code? What is the recommended solution here?

Thank you

int moduleA_create(moduleA *handle, int param1, int param2, int param3, int param4);
int moduleA_destroy(moduleA handle);
int moduleB_create(moduleB *handle, const char *param);
int moduleB_destroy(moduleB handle);

int some_function(void) {
  int r;
  moduleA a;
  moduleB b;

  r = moduleA_create(&a, 1, 2, 3, 4);
  if (r < 0) goto EXIT_A;

  r = moduleB_create(&b, "some string");
  if (r < 0) goto EXIT_B;

  // more code

  // this could return error
  moduleB_destroy(b);
EXIT_B:
  // this could return error
  moduleA_destroy(a);
EXIT_A:

  return r;
}

CodePudding user response:

Do you just log but ignore the error otherwise, do you return the error code of the *_destroy function at the end? Would you somehow restructure the code? What is the recommended solution here?

It's highly situation-dependent, as, indeed, is error-handling in general. Often, there's not much you can do other than emit messages / different return values, and possibly abort the program.

If the error happens in the context of shutting down the application, as your particular example suggests to me, then I would be inclined to attempt to continue the standard shutdown sequence. A diagnostic message to stderr might be warranted. A non-zero exit status might be warranted. Or not.

For example, I usually just ignore failures to close() / fclose() files that were only read, but I would definitely emit a warning or error message on failing to close a file that the program had created or written to, on account of the possibility that the program's operations on the file might not have actually been carried out as intended.

CodePudding user response:

"Would you somehow restructure the code?"

Rather than using goto with its opportunity for code to be wrong, sequenced progress may require undoing what's been done (as best one can) in exactly the reverse order.

Below is a sketch that demonstrates one "successful" trial run, and one run deliberately coded to fail on the 3rd step. It uses the conditional operator for brevity, and always the return code "-1" from teardown functions. This is just a sketch.

Notice that the progress/teardown function invocations are proximate to one another and that the sequence reverses itself.

This code would, of course, be altered to meet needs. It provides a schema that does not require goto labels to be ordered in code, distant from where they may be "used". This is one alternative to "restructuring the code".

int doPh1R() { puts( "doPh1R" ); return -1; }
int doPh1F() { puts( "doPh1F" ); return 1; }

int doPh2R() { puts( "doPh2R" ); return -1; }
int doPh2F() { puts( "doPh2F" ); return 1; }

int doPh3R() { puts( "doPh3R" ); return -1; }
int doPh3F() {
    static bool cond = true;
    if( cond ) {
        puts( "doPh3F" );
        cond = !cond; // boobytrap the next execution
        return 1;
        }

    puts( "doPh3F Error" );
    return -1;
}

int doSomething( int trial ) {
    enum { done, phase1, phase2, phase3, run };

    int dir = 1;
    int err = -1; // pessimism
    printf( "\tTrial #%d\n", trial );
    for( int phase = phase1; phase != done; phase  = dir )
        switch( phase ) {
            case phase1:
                dir = dir==1 ? doPh1F() : doPh1R();
                break;
            case phase2:
                dir = dir==1 ? doPh2F() : doPh2R();
                break;
            case phase3:
                dir = dir==1 ? doPh3F() : doPh3R();
                break;
            case run:
                puts( "run" );
                dir = -1;
                err = 0;
                break;
        }
    return err;
}

int main() {
    puts( doSomething(1) ? "\tFail" : "\tSuccess" ); // One good run
    puts( doSomething(2) ? "\tFail" : "\tSuccess" ); // One bad run

    return 0;
}

Output

        Trial #1
doPh1F
doPh2F
doPh3F
run
doPh3R
doPh2R
doPh1R
        Success
        Trial #2
doPh1F
doPh2F
doPh3F Error
doPh2R
doPh1R
        Fail
  •  Tags:  
  • c
  • Related