scipopt / scip

SCIP - Solving Constraint Integer Programs
Other
406 stars 67 forks source link

Data race in interrupt.c #72

Open lperron opened 1 year ago

lperron commented 1 year ago

SanitizerError ThreadSanitizer: data race [scip/src/scip/interrupt.c:167]

SCIP 8.0.4

svigerske commented 1 year ago

The SCIP library, unfortunately, still registers a signal handler that looks for SIGINT by default. You probably want to disable this by setting parameter misc/catchctrlc to FALSE and handle interrupts from the calling code.

lperron commented 1 year ago

I do think this is the issue. IMO, the problem is that the counter is not MT-Safe. I could be wrong.

svigerske commented 1 year ago

Yes, the interrupt counting makes little sense in a multithreaded setting. You can have SCIP in one thread catching SIGINT and incrementing ninterrupts and short after have a SCIP in a different thread decide to reset ninterrupts to 0 (SCIPinterruptCapture()) before any SCIP in any thread would realize that they all should stop.

Therefore, the current workaround is to set misc/catchctrlc to FALSE.

That several threads read the same ninterrupts ([scip/src/scip/interrupt.c:167]) is not so much of a problem and I assume that the idea of this SIGINT handling was to terminate SCIP's in all threads and not only the one that catched the signal by coincidence.

lperron commented 1 year ago

I fixed it using stdatomic.h

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*                  This file is part of the program and library             */
/*         SCIP --- Solving Constraint Integer Programs                      */
/*                                                                           */
/*  Copyright (c) 2002-2023 Zuse Institute Berlin (ZIB)                      */
/*                                                                           */
/*  Licensed under the Apache License, Version 2.0 (the "License");          */
/*  you may not use this file except in compliance with the License.         */
/*  You may obtain a copy of the License at                                  */
/*                                                                           */
/*      http://www.apache.org/licenses/LICENSE-2.0                           */
/*                                                                           */
/*  Unless required by applicable law or agreed to in writing, software      */
/*  distributed under the License is distributed on an "AS IS" BASIS,        */
/*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/*  See the License for the specific language governing permissions and      */
/*  limitations under the License.                                           */
/*                                                                           */
/*  You should have received a copy of the Apache-2.0 license                */
/*  along with SCIP; see the file LICENSE. If not visit scipopt.org.         */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**@file   interrupt.c
 * @ingroup OTHER_CFILES
 * @brief  methods and datastructures for catching the user CTRL-C interrupt
 * @author Tobias Achterberg
 */

/*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/

#include <assert.h>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <stdatomic.h>

#include "scip/def.h"
#include "scip/pub_message.h"
#include "blockmemshell/memory.h"
#include "scip/interrupt.h"

static atomic_int                      ninterrupts = 0;    /**< static variable counting the number of CTRL-C interrupts */
static atomic_int                      nterms = 0;         /**< static variable counting the number of times that the process received a SIGTERM signal */

#ifdef SCIP_NO_SIGACTION
typedef void (*SigHdlr)(int);

/** CTRL-C interrupt data */
struct SCIP_Interrupt
{
   SigHdlr               oldsighdlr;         /**< old CTRL-C interrupt handler */
   int                   nuses;              /**< number of times, the interrupt is captured */
};

#else

/** CTRL-C interrupt data */
struct SCIP_Interrupt
{
   struct sigaction      oldsigaction;       /**< old CTRL-C interrupt handler */
   int                   nuses;              /**< number of times, the interrupt is captured */
};
#endif

/** interrupt handler for CTRL-C interrupts */
static
void interruptHandler(
   int                   signum              /**< interrupt signal number */
   )
{
   SCIP_UNUSED(signum);

   int real_ninterrupts = atomic_fetch_add (&ninterrupts, 1);
   if( real_ninterrupts >= 5 )
   {
      printf("pressed CTRL-C %d times. forcing termination.\n", real_ninterrupts);
      exit(1);
   }
   else
   {
      printf("pressed CTRL-C %d times (5 times for forcing termination)\n", real_ninterrupts);
   }
}

/** creates a CTRL-C interrupt data */
SCIP_RETCODE SCIPinterruptCreate(
   SCIP_INTERRUPT**      interrupt           /**< pointer to store the CTRL-C interrupt data */
   )
{
   assert(interrupt != NULL);

   SCIP_ALLOC( BMSallocMemory(interrupt) );
   (*interrupt)->nuses = 0;

   return SCIP_OKAY;
}

/** frees a CTRL-C interrupt data */
void SCIPinterruptFree(
   SCIP_INTERRUPT**      interrupt           /**< pointer to the CTRL-C interrupt data */
   )
{
   assert(interrupt != NULL);

   BMSfreeMemory(interrupt);
}

/** captures the CTRL-C interrupt to call the SCIP's own interrupt handler */
void SCIPinterruptCapture(
   SCIP_INTERRUPT*       interrupt           /**< CTRL-C interrupt data */
   )
{
   assert(interrupt != NULL);
   assert(interrupt->nuses >= 0);

   if( interrupt->nuses == 0 )
   {
#ifdef SCIP_NO_SIGACTION
      interrupt->oldsighdlr = signal(SIGINT, interruptHandler);
#else
      struct sigaction newaction;

     /* initialize new signal action */
      newaction.sa_handler = interruptHandler;
      (void)sigemptyset(&newaction.sa_mask);

      /* preserve SA_ONSTACK if needed */
      interrupt->oldsigaction.sa_flags = 0;
      (void)sigaction(SIGINT, NULL, &interrupt->oldsigaction);
      newaction.sa_flags = interrupt->oldsigaction.sa_flags & SA_ONSTACK;

      /* set new signal action, and remember old one */
      (void)sigaction(SIGINT, &newaction, &interrupt->oldsigaction);
#endif

      atomic_store(&ninterrupts, 0);
      atomic_store(&nterms, 0);
   }
   interrupt->nuses++;
}

/** releases the CTRL-C interrupt and restores the old interrupt handler */
void SCIPinterruptRelease(
   SCIP_INTERRUPT*       interrupt           /**< CTRL-C interrupt data */
   )
{
   assert(interrupt != NULL);
   assert(interrupt->nuses >= 1);

   interrupt->nuses--;
   if( interrupt->nuses == 0 )
   {
#ifdef SCIP_NO_SIGACTION
      (void)signal(SIGINT, interrupt->oldsighdlr);
#else
      (void)sigaction(SIGINT, &interrupt->oldsigaction, NULL);
#endif
   }
}

/** returns whether the user interrupted by pressing CTRL-C */
SCIP_Bool SCIPinterrupted(
   void
   )
{
   return (atomic_load(&ninterrupts) > 0);
}

/** returns whether a process termination signal was received */
SCIP_Bool SCIPterminated(
   void
   )
{
   return (atomic_load(&nterms) > 0);
}

/** sends a termination signal to all SCIP processes so that they try to terminate as soon as possible
 *
 *  @note For terminating a specific SCIP process use SCIPinterruptSolve().
 */
void SCIPtryTerminate(
   void
   )
{
   atomic_fetch_add(&nterms, 1);
}

/** resets the number of interrupts to 0 */
void SCIPresetInterrupted(
   void
   )
{
   atomic_store(&ninterrupts, 0);
   atomic_store(&nterms, 0);
}