Re: [c++-pthreads] Restating the Jason model
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [c++-pthreads] Restating the Jason model



Wil Evers wrote:

Jason Merrill wrote:

I think this is an appropriate time to restate my proposal.  I think
there's a fair amount of consensus around these three points:

 * Cancellation is a normal exception.
 * If a cancellation exception is destroyed, the cancellation request
   is re-entered, and acted on again at the next cancellation point.

I can't help wondering about the difference between this design and Nathan's sticky cancellation model. It seems to me that in your model, I can do something like:

  catch (const cancellation_request&) {
    some_socket.write("Thread cancelled\n");
  }

and expect the write operation to succeed, whereas in Nathan's sticky cancellation design, the write operation will throw another cancellation_request (unless, of course, I disable cancellation in the catch block). If so, is this intentional?

Absolutely; because the correct behavior of the application (which might be distributed) could depend on being able to notify the remote partner that the local thread is shutting down. Nathan's proposal doesn't allow that, and that's the big weakness.

Jason's proposal will cause the cancel to be re-asserted when the exception object is destroyed, on exit from the catch(), after local cleanup has been done. This makes the catch() behave (more or less) like a destructor.

And what about this one:

  try {
    some_socket.connect("www.ibm.com");
  } catch (const cancellation_request&) {
    throw my_fancy_exception(__FILE__, __LINE__,
      "Cancellation request");
  }

Here, unwinding continues, but the cancellation request is mapped onto some other - presumably legacy - exception hierarchy. Do we really want the cancellation request to be re-entered here?

Yes, perhaps; because propagation of my_fancy_exception would either be swallowed later (probably, since nobody would have thrown it without a catch) or else it'll terminate the process. Neither meets the semantic definition of cancellation, or the expectations of the application code that sent the cancel.

Of course, you could get arbitrarily convoluted here, my somehow making the propagation of my_fancy_exception inherit the "progagating cancel" state so that when THAT object is destroyed the original cancel will be re-raised, or so detection of THAT unhandled exception will terminate the thread rather than the process. (Though I'm not at all convinced that's what we'd want to happen.)

One alternative is to simply say that such code is NOT cancel-safe; and that the behavior of a thread running such code when cancelled is inherently unreliable. A lot of existing code won't be cancel-safe, and I don't believe that there's anything the standard or any implementation can do about that.

To me, it seems like Nathan's sticky cancellation model uses a tried-and-tested design - it simply puts the thread object into an error state, causing subsequent operations to fail - whereas your design is quite innovative. I'm not suggesting there is anything wrong with that, but I do believe it needs to be justified.

There's no such thing, currently, as an error condition that will cause ALL subsequent blocking operations to fail. It's not at all "tried and tested".

Aside from the "sticky" characteristic, which seems to be an almost necessary consequence of the C++ catch(...) syntax and idiom, Jason's proposal is exactly the model for which POSIX cancellation was designed, and which has been in wide use for up to 12 years. (Starting with our CMA library on VMS and ULTRIX, and extending out through POSIX standardization to most other UNIX-based systems.)

 * Should cancellation also be disabled in destructors run during normal
   execution?  In catch blocks?

IMO, no and no.

This implies a difference in how destructors behave, depending on why they are invoked. If such a difference can be avoided, I think it should be: we have Ted Baker's model (destructors always disable cancellation), and the other obvious choice is to leave it to the user to take the necessary precautions when writing destructors. Good C++ programmers already know how to do that.

They may know how, but most haven't previously had any need to worry about that. It seems to be fairly widely agreed here that propagating an exception out of a destructor is "bad". Since that's what will happen if cancellation strikes an unprepared destructor, it seems to me that the best option is to prevent that in the default case (by disabling cancellation), and requiring any destructor that really WANTS to be cancellable to do something unusual.

--
/--------------------[ David.Butenhof@xxxxxx ]--------------------\
| Hewlett-Packard Company       Tru64 UNIX & VMS Thread Architect |
|     My book: http://www.awl.com/cseng/titles/0-201-63392-2/     |
\----[ http://homepage.mac.com/dbutenhof/Threads/Threads.html ]---/