2

I'm looking for a way to exit sleep when an user interrupt arrives. It's important to exit sleep rather than do this: interrupt sleep, do ISR processing, and go back to sleep - which is what I'm finding solutions for.

I'm looking for something like this in C++ - an equivalent in C is even better:

void *timer_thread(void *dummy)
{
  while(1)
  {
    // Check if some callbacks are to be given
    // when all are given, Determine x duration to sleep
  
    try
    {
      sleep(x);
    }
    except(/* the except block should hit ONLY when an interrupt arrives, 
              that too only when sleep() is executed. It's OK to delay 
              interrupt until the sleep is beginning execution */) 
    {
      //Do something else
    }
  }
}

The arriving interrupt will mostly tell that the sleep should be reduced for the timer thread to give callbacks earlier. But irrespective of the use case, I just need the sleep to be exited in some way when interrupt arrives. I just couldn't find info on how to do this.

PS: It's ok to discard/NOP the interrupts if it occurs when sleep wasn't happening

This is on Cygwin gcc v9.3.0 on Windows 10 (C/C++). I don't need the code to be portable, so any platform specific solution is ok as well.

If there's some other solution which is similar to this working (which doesn't use sleep() and interrupts), would be welcome to hear it. I'm just looking for a way which doesn't involve polling.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Arun
  • 131
  • 1
  • 12
  • This sounds somehow hopeless. With sleep, you tell the OS scheduler to suspend your thread for a certain time. I don't believe there is a back-door. To mix a delay with another condition, `std::mutex` with [std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable) comes into mind. Not sure, how this goes together with the interrupt service routine you mentioned. – Scheff's Cat Apr 29 '20 at 05:13
  • if there's some other solution to above, I'm open to hear it. It doesnt need to be sleep() function, nor does it have to be an interrupt. – Arun Apr 29 '20 at 05:32
  • I don't know if it's available in Cygwin, but one possible solution in Linux would be to (ab)use `epoll_wait()`. This would wait for some event to occur on a set of file descriptors, or until a timeout occurs. So in principle you could use the epoll timeout to replace `sleep()`, but abort it by writing to the file descriptor from somewhere else. And if epoll doesn't exist in Cygwin, then maybe there's something similar you can use – Felix G Apr 29 '20 at 07:11

3 Answers3

4

To wait for a certain time or on a certain event, I would use a combination of std::mutex and std::condition_variable and specifically std::condition_variable::wait_for() to await either time-out or a signaled change of something.

A minimal sample program for demonstration:

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;

// a thread-shared flag to signal exit
std::atomic<bool> exitThread = false;

// a mutex to sync. inter-thread communication
std::mutex mtxAlert;
// a condition variable to signal changed data
std::condition_variable sigAlert;
// the data of inter-thread communication
bool alert = false;

void timerThread()
{
  // the timeout for timer thread
  auto timeout = 100ms;
  // runtime loop
  while (!exitThread) {
    // lock mutex (preparation to wait in cond. var.)
    std::unique_lock<std::mutex> lock(mtxAlert);
    // unlock mutex and wait for timeout or signaled alert
    sigAlert.wait_for(lock, timeout, []() { return alert || exitThread; });
    // mutex is locked again
    // check why wait_for() exited
    if (exitThread) {
      std::cout << "Timer thread exiting...\n";
      return;
    } else if (alert) {
      std::cout << "Timer was interrupted due to alert.\n";
      alert = false;
    } else {
      std::cout << "Timer achieved time-out.\n";
    }
  }
}

int main()
{
  std::thread threadWait(&timerThread);
  // wait a bit
  std::cout << "main(): Waiting 300ms...\n";
  std::this_thread::sleep_for(300ms);
  // sim. interrupt
  std::cout << "main(): Sim. interrupt.\n";
  { std::lock_guard<std::mutex> lock(mtxAlert);
    alert = true;
  }
  sigAlert.notify_all();
  // wait a bit
  std::cout << "main(): Waiting 50 ms...\n";
  std::this_thread::sleep_for(50ms);
  // sim. interrupt
  std::cout << "main(): Sim. interrupt.\n";
  { std::lock_guard<std::mutex> lock(mtxAlert);
    alert = true;
  }
  sigAlert.notify_all();
  // wait a bit
  std::cout << "main(): Waiting 50 ms...\n";
  std::this_thread::sleep_for(50ms);
  // exiting application
  exitThread = true;
  sigAlert.notify_all();
  threadWait.join();
  // done
  std::cout << "Done.\n";
}

Output:

main(): Waiting 300ms...
Timer achieved time-out.
Timer achieved time-out.
main(): Sim. interrupt.
main(): Waiting 50 ms...
Timer was interrupted due to alert.
main(): Sim. interrupt.
main(): Waiting 50 ms...
Timer was interrupted due to alert.
Timer thread exiting...
Done.

Live Demo on coliru


OP claimed per comment that this sample didn't compile properly on cygwin. I tried on my side and can confirm some minor issues which I fixed:

  1. Missing #include <mutex> added

  2. Initialization of std::atomic<bool> exitThread = false; changed to

    std::atomic<bool> exitThread(false);
    

    I got this when I compiled with g++ as well as with g++ -std=c++14. (It seems that -std=c++14 is the default of my g++.)

    When I use g++ -std=c++17 instead I don't get any complaint. I strongly believe that has something to do with copy-elision which g++ applies with -std=c++17 but not prior.

However, this is my sample session with the slightly reviewed code on my Windows 10 laptop in cygwin64:

$ g++ --version
g++ (GCC) 7.4.0

$
$ cat >testCondVar.cc <<'EOF'
> #include <atomic>
> #include <condition_variable>
> #include <iostream>
> #include <mutex>
> #include <thread>
> #include <chrono>
> using namespace std::chrono_literals;
> 
> // a thread-shared flag to signal exit
> std::atomic<bool> exitThread(false);
> 
> // a mutex to sync. inter-thread communication
> std::mutex mtxAlert;
> // a condition variable to signal changed data
> std::condition_variable sigAlert;
> // the data of inter-thread communication
> bool alert = false;
> 
> void timerThread()
> {
>   // the timeout for timer thread
>   auto timeout = 100ms;
>   // runtime loop
>   while (!exitThread) {
>     // lock mutex (preparation to wait in cond. var.)
>     std::unique_lock<std::mutex> lock(mtxAlert);
>     // unlock mutex and wait for timeout or signaled alert
>     sigAlert.wait_for(lock, timeout, []() { return alert || exitThread; });
>     // mutex is locked again
>     // check why wait_for() exited
>     if (exitThread) {
>       std::cout << "Timer thread exiting...\n";
>       return;
>     } else if (alert) {
>       std::cout << "Timer was interrupted due to alert.\n";
>       alert = false;
>     } else {
>       std::cout << "Timer achieved time-out.\n";
>     }
>   }
> }
> 
> int main()
> {
>   std::thread threadWait(&timerThread);
>   // wait a bit
>   std::cout << "main(): Waiting 300ms...\n";
>   std::this_thread::sleep_for(300ms);
>   // sim. interrupt
>   std::cout << "main(): Sim. interrupt.\n";
>   { std::lock_guard<std::mutex> lock(mtxAlert);
>     alert = true;
>   }
>   sigAlert.notify_all();
>   // wait a bit
>   std::cout << "main(): Waiting 50 ms...\n";
>   std::this_thread::sleep_for(50ms);
>   // sim. interrupt
>   std::cout << "main(): Sim. interrupt.\n";
>   { std::lock_guard<std::mutex> lock(mtxAlert);
>     alert = true;
>   }
>   sigAlert.notify_all();
>   // wait a bit
>   std::cout << "main(): Waiting 50 ms...\n";
>   std::this_thread::sleep_for(50ms);
>   // exiting application
>   exitThread = true;
>   sigAlert.notify_all();
>   threadWait.join();
>   // done
>   std::cout << "Done.\n";
> }
> EOF

$

Compiled and started:

$ g++ -std=c++14 -o testCondVar testCondVar.cc

$ ./testCondVar
main(): Waiting 300ms...
Timer achieved time-out.
Timer achieved time-out.
main(): Sim. interrupt.
main(): Waiting 50 ms...
Timer was interrupted due to alert.
main(): Sim. interrupt.
main(): Waiting 50 ms...
Timer was interrupted due to alert.
Timer thread exiting...
Done.

$

Note:

The only reason that at minimum C++14 is required for this sample is the usage of the std::chrono_literals which enables the usage of e.g. 300ms.

Without std::chrono_literals, this could be written as std::chrono::milliseconds(300) (which is admittedly less convenient). Replacing all std::chrono_literals respectively, I was able to compile and run the sample with -std=c++11 as well.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • This code is giving build errors in my workspace. Does it have any prerequisites? Line 4: ..\Offtarget Definition\src\ot_timer.cpp:31:32: error: use of deleted function 'std::atomic::atomic(const std::atomic&)' Line 14: ..\Offtarget Definition\src\ot_timer.cpp:34:6: error: 'mutex' in namespace 'std' does not name a type Line 21: ..\Offtarget Definition\src\ot_timer.cpp:36:6: error: 'condition_variable' in namespace 'std' does not name a type Line 49: ..\Offtarget Definition\src\ot_timer.cpp:255:27: error: 'mutex' is not a member of 'std' ... to name a few – Arun Apr 29 '20 at 16:53
  • @Arun `std::mutex` requires `#include `. In my sample, it may be included indirectly. (As you can see in the link, it compiles and runs fine with g++ on Linux. The version of g++ on coliru is currently 9.2.) Tomorrow, I try to comile and run it in cygwin on my box. – Scheff's Cat Apr 29 '20 at 20:25
  • @Arun Answer reviewed, issues fixed. – Scheff's Cat Apr 30 '20 at 05:44
  • Thanks @Scheff, I had added the #include before compiling again, but had seen the problem again, hence asked. the C++14 actually fixed the problem - I hadn't set a default actually. I'll test this more throughly with different usecases and will get back – Arun Apr 30 '20 at 08:47
  • Any idea why this error can have occurred?: ..\Offtarget Definition\src\ot_timer.cpp:291:8: error: 'std::this_thread' has not been declared – Arun Apr 30 '20 at 17:21
  • I don't understand why you get `std::this_thread` has not been declared. `#include ` should be sufficient for this. If you look at my compile line you see that I don't include or link something exotic but just the std libraries. g++ and standard libraries are out of the standard cygwin setup (a bit aged maybe but this shouldn't be an issue). – Scheff's Cat Apr 30 '20 at 18:01
  • I figured out the problem - I think gcc 9.3.0 is missing mutex, threads and conditional locks. All are not being compiled as _GLIBCXX_HAS_GTHREADS is not defined. I'm not sure how to fix this... any idea folks? – Arun May 02 '20 at 06:32
  • @Arun Please, note that the sample on [coliru](http://coliru.stacked-crooked.com/a/46caed6b08b99d74) uses `-pthread`. I was uncertain whether I would need this in cygwin as well but (just by trying) came to the conclusion that it works without in my case. I'm not sure why this could be different in your cygwin. (I always mentioned that I just installed everything by the cygwin setup.) However, that might be subject of some config. and you could try with `-pthread`. FYI: [SO: GCC std::thread not found in namespace std](https://stackoverflow.com/a/3417007/7478597) (though it is really aged). – Scheff's Cat May 02 '20 at 06:40
  • @Arun Do you use the mingw compilers in cygwin? I don't (and I don't have any experience with this) but found this forum thread: [Narkive: How to detect which threading model is used with the preprocessor?](https://mingw-w64-public.narkive.com/O21Q5L0J/how-to-detect-which-threading-model-is-used-with-the-preprocessor) which might give some insights. – Scheff's Cat May 02 '20 at 06:46
  • I've manually downloaded gcc through cygwin (v9.3.0) and have built it in my PC. I've tried searching for thread support, but I'm not getting any info on this ... – Arun May 02 '20 at 06:48
  • @Arun I must admit that I don't fully understand all the config. options all these tools usually provide (and I'm less interested in it). That's why, I'm used (and trust) to just install the "standard setups" in the hope that any common stuff will work as expected. :-) That enables me to get into C++ coding and algorithms carving as soon as possible (that I'm actually interested in). In the rare exceptional cases, I do like you - asking somebody else with different preferences. So, I'm afraid I can't provide better advise for your issue... – Scheff's Cat May 02 '20 at 06:55
  • absolutely no probs - I totally understand. I think your solution works fine. It's just that my cygwin isnt having this support. I'll try rebuilding it - maybe it was broken in the first build (had some trouble getting it to build initially). – Arun May 02 '20 at 17:12
0

I'm not familiar with thread libraries for windows. So I can give a pseudo-code instead. You should be able to implement it in C as well as C++. (The syntax of code below is obviously wrong)

Create a separate thread for sleep to execute

void * mySleepThread(int x,mutex m)
{
  sleep(x);
  if(m.isNotDestroyed)
  {
     m.setValue(unlocked);
  }
  return;
}

And your main timer thread would look something like:

void * timer_thread(void*dummy)
{
  while(1)
  {
    // Check if some callbacks are to be given
    // when all are given, Determine x duration to sleep


    create_mutex m1;
    m1.setValue(locked);

    //The Try sleep(x) block
    ExecuteThread(mySleepThread(x,m1));

    //The except block
    while(1)
    {
      if(m1.isNotDestroyed && m1.isLocked && interruptDetected)
        {
          m1.destroy;
          //Execute the interrupt statements here
          break;
        }

       else if(m1.isNotDestroyed && m1.isUnlocked)
       {
         m1.destroy;
         //Execute block of sleep finished without interrupt
         break;
       }
    }


    //Do something else block

  }
}
Abhay Aravinda
  • 878
  • 6
  • 17
0

I had to implement sleep that could be interrupted via other thread, so I came up with the following code:

#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>

class InterruptibleTimer {
 public:
  InterruptibleTimer() : m_interrupted(false) {}

  template <class Rep, class Period>
  int sleep_for(const std::chrono::duration<Rep, Period>& delay) {

    /* Use wait_for timeout for sleeping. */
    std::unique_lock<std::mutex> lk(m_cv_mutex);
    if (m_cv.wait_for(lk, delay, [&, this]{return m_interrupted == true;})) {
      /* Timer was interrupted. */
      return 1;
    }

    /* Timer was finished. */
    return 2;
  }

  void interrupt() {
    m_cv.notify_all();
    {
      std::lock_guard<std::mutex> lk(m_cv_mutex);
      m_interrupted = true;
    }
    m_cv.notify_all();
    return;
  }

 private:
  std::condition_variable m_cv;
  std::mutex m_cv_mutex;
  std::atomic<bool> m_interrupted;
};

Example usage:

#include <thread>

void interrupt_after_2s(InterruptibleTimer& timer) {
  std::this_thread::sleep_for(std::chrono::seconds(2));
  timer.interrupt();
  return;
}

int main() {
  InterruptibleTimer timer;
  std::thread t1(interrupt_after_2s, std::ref(timer));
  timer.sleep_for(std::chrono::seconds(10));
  t1.join();
  return 0;
}

DEMO

$ clang++ -lpthread demo.cc -o demo
$ time ./demo
./demo  0.00s user 0.00s system 0% cpu 2.002 total
andrzej1_1
  • 1,151
  • 3
  • 21
  • 38