Discussion:
[3.3] Followup to C++ forced unwinding
(too old to reply)
Richard Henderson
2003-04-30 17:53:35 UTC
Permalink
The result of the original thread

http://gcc.gnu.org/ml/gcc-patches/2003-04/msg00008.html

endorsed different semantics than I assumed in my original
patch. Here I update things to match the consensus.

Mark, I know it's getting late, but is this ok for 3.3?
(There'll be a pr shortly; wanted to include a pointer to
this message in the description text though.)


r~


gcc/
* except.c (RNL_ALWAYS_CAUGHT): New.
(expand_eh_region_end_cleanup): Ignore flag_forced_unwind_exceptions.
(convert_from_eh_region_ranges_1): Likewise.
(build_post_landing_pads): Likewise.
(sjlj_find_directly_reachable_regions): Likewise.
(collect_one_action_chain): Likewise.
(reachable_next_level): Return RNL_ALWAYS_CAUGHT for empty filter
spec and must_not_throw shadowing other catch handlers.
(reachable_handlers): With forced unwinding, only mark cleanups
live after catch(...), not throw().
(can_throw_external): Similarly wrt exceptions escaping the function.

gcc/cp/
* Make-lang.in (cfns.h): Add -t to gperf invocation.
* cfns.gperf: Use a structure; mark POSIX cancellation points.
* except.c (nothrow_libfn_p): Return bool; return false for
cancellation points when forced unwinding in effect.
* cp-tree.h (nothrow_libfn_p): Update declaration.

gcc/testsuite/
* g++.dg/eh/forced2.C (force_unwind): Remove nothrow spec.

libstdc++-v3/
* libsupc++/eh_personality.cc (empty_exception_spec): New.
(PERSONALITY_FUNCTION): Ignore FORCE_UNWIND when considering
must_not_throw barriers; allow empty filter specs to signal
unexpected on FORCE and foreign exception types.

Index: gcc/except.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/except.c,v
retrieving revision 1.233.2.4
diff -c -p -d -u -r1.233.2.4 except.c
--- gcc/except.c 22 Apr 2003 15:50:54 -0000 1.233.2.4
+++ gcc/except.c 30 Apr 2003 17:34:44 -0000
@@ -1,6 +1,6 @@
/* Implements exception handling.
Copyright (C) 1989, 1992, 1993, 1994, 1995, 1996, 1997, 1998,
- 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
+ 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
Contributed by Mike Stump <***@cygnus.com>.

This file is part of GCC.
@@ -324,8 +324,12 @@ enum reachable_code
RNL_NOT_CAUGHT,
/* The given exception may need processing by the given region. */
RNL_MAYBE_CAUGHT,
- /* The given exception is completely processed by the given region. */
+ /* The given exception is completely processed by the given region,
+ via catch clauses. */
RNL_CAUGHT,
+ /* The given exception is completely processed by the given region,
+ via empty eh filter specs or must_not_throw clauses. */
+ RNL_ALWAYS_CAUGHT,
/* The given exception is completely processed by the runtime. */
RNL_BLOCKED
};
@@ -562,9 +566,7 @@ expand_eh_region_end_cleanup (handler)

emit_label (region->label);

- if (flag_non_call_exceptions
- || flag_forced_unwind_exceptions
- || region->may_contain_throw)
+ if (flag_non_call_exceptions || region->may_contain_throw)
{
/* Give the language a chance to specify an action to be taken if an
exception is thrown that would propagate out of the HANDLER. */
@@ -1140,16 +1142,10 @@ convert_from_eh_region_ranges_1 (pinsns,
/* An existing region note may be present to suppress
exception handling. Anything with a note value of -1
cannot throw an exception of any kind. A note value
- of 0 means that "normal" exceptions are suppressed,
- but not necessarily "forced unwind" exceptions. */
+ of 0 means that "normal" exceptions are suppressed. */
note = find_reg_note (insn, REG_EH_REGION, NULL_RTX);
if (note)
- {
- if (flag_forced_unwind_exceptions
- && INTVAL (XEXP (note, 0)) >= 0)
- XEXP (note, 0) = GEN_INT (cur);
- break;
- }
+ break;

/* Calls can always potentially throw exceptions; if we wanted
exceptions for non-call insns, then any may_trap_p
@@ -1793,33 +1789,8 @@ build_post_landing_pads ()
break;

case ERT_CLEANUP:
- region->post_landing_pad = region->label;
- break;
-
case ERT_MUST_NOT_THROW:
- /* See maybe_remove_eh_handler about removing region->label. */
- if (flag_forced_unwind_exceptions && region->label)
- {
- region->post_landing_pad = gen_label_rtx ();
-
- start_sequence ();
-
- emit_label (region->post_landing_pad);
- emit_cmp_and_jump_insns (cfun->eh->filter, const0_rtx, GT,
- NULL_RTX, word_mode, 0, region->label);
-
- region->resume
- = emit_jump_insn (gen_rtx_RESX (VOIDmode,
- region->region_number));
- emit_barrier ();
-
- seq = get_insns ();
- end_sequence ();
-
- emit_insn_before (seq, region->label);
- }
- else
- region->post_landing_pad = region->label;
+ region->post_landing_pad = region->label;
break;

case ERT_CATCH:
@@ -2000,21 +1971,7 @@ sjlj_find_directly_reachable_regions (lp
break;
}

- /* Forced unwind exceptions aren't blocked. */
- if (flag_forced_unwind_exceptions && rc == RNL_BLOCKED)
- {
- struct eh_region *r;
- for (r = region->outer; r ; r = r->outer)
- if (r->type == ERT_CLEANUP)
- {
- rc = RNL_MAYBE_CAUGHT;
- if (! region->label)
- region = r;
- break;
- }
- }
-
- if (rc == RNL_MAYBE_CAUGHT || rc == RNL_CAUGHT)
+ if (rc == RNL_MAYBE_CAUGHT || rc == RNL_CAUGHT || rc == RNL_ALWAYS_CAUGHT)
{
lp_info[region->region_number].directly_reachable = 1;
found_one = true;
@@ -2752,7 +2709,7 @@ reachable_next_level (region, type_throw
if (region->u.allowed.type_list == NULL_TREE)
{
add_reachable_handler (info, region, region);
- return RNL_CAUGHT;
+ return RNL_ALWAYS_CAUGHT;
}

/* Collect a list of lists of allowed types for use in detecting
@@ -2791,7 +2748,7 @@ reachable_next_level (region, type_throw
if (info && info->handlers)
{
add_reachable_handler (info, region, region);
- return RNL_CAUGHT;
+ return RNL_ALWAYS_CAUGHT;
}
else
return RNL_BLOCKED;
@@ -2851,10 +2808,11 @@ reachable_handlers (insn)

while (region)
{
- if (reachable_next_level (region, type_thrown, &info) >= RNL_CAUGHT)
+ /* Forced unwind exceptions may be BLOCKED but not CAUGHT.
+ Make sure the cleanup regions are reachable. */
+ switch (reachable_next_level (region, type_thrown, &info))
{
- /* Forced unwind exceptions are neither BLOCKED nor CAUGHT.
- Make sure the cleanup regions are reachable. */
+ case RNL_CAUGHT:
if (flag_forced_unwind_exceptions)
{
while ((region = region->outer) != NULL)
@@ -2864,6 +2822,13 @@ reachable_handlers (insn)
break;
}
}
+ goto fini;
+
+ case RNL_ALWAYS_CAUGHT:
+ case RNL_BLOCKED:
+ goto fini;
+
+ default:
break;
}

@@ -2876,6 +2841,7 @@ reachable_handlers (insn)
else
region = region->outer;
}
+ fini:

return info.handlers;
}
@@ -2988,10 +2954,6 @@ can_throw_external (insn)
if (INTVAL (XEXP (note, 0)) <= 0)
return false;

- /* Forced unwind excptions are not catchable. */
- if (flag_forced_unwind_exceptions && GET_CODE (insn) == CALL_INSN)
- return true;
-
region = cfun->eh->region_array[INTVAL (XEXP (note, 0))];

type_thrown = NULL_TREE;
@@ -3004,8 +2966,18 @@ can_throw_external (insn)
/* If the exception is caught or blocked by any containing region,
then it is not seen by any calling function. */
for (; region ; region = region->outer)
- if (reachable_next_level (region, type_thrown, NULL) >= RNL_CAUGHT)
- return false;
+ switch (reachable_next_level (region, type_thrown, NULL))
+ {
+ case RNL_CAUGHT:
+ if (!flag_forced_unwind_exceptions)
+ break;
+ /* FALLTHRU */
+ case RNL_ALWAYS_CAUGHT:
+ case RNL_BLOCKED:
+ return false;
+ default:
+ break;
+ }

return true;
}
@@ -3410,13 +3382,6 @@ collect_one_action_chain (ar_hash, regio
requires no call-site entry. Note that this differs from
the no handler or cleanup case in that we do require an lsda
to be generated. Return a magic -2 value to record this. */
- if (flag_forced_unwind_exceptions)
- {
- struct eh_region *r;
- for (r = region->outer; r ; r = r->outer)
- if (r->type == ERT_CLEANUP)
- return 0;
- }
return -2;

case ERT_CATCH:
Index: gcc/cp/Make-lang.in
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/Make-lang.in,v
retrieving revision 1.125.4.3
diff -c -p -d -u -r1.125.4.3 Make-lang.in
--- gcc/cp/Make-lang.in 13 Feb 2003 07:17:09 -0000 1.125.4.3
+++ gcc/cp/Make-lang.in 30 Apr 2003 17:34:45 -0000
@@ -98,7 +98,7 @@ cc1plus$(exeext): $(CXX_OBJS) $(CXX_C_OB

# Special build rules.
$(srcdir)/cp/cfns.h: $(srcdir)/cp/cfns.gperf
- gperf -o -C -E -k '1-6,$$' -j1 -D -N 'libc_name_p' \
+ gperf -o -C -E -k '1-6,$$' -j1 -D -N 'libc_name_p' -t \
$(srcdir)/cp/cfns.gperf > $(srcdir)/cp/cfns.h

$(srcdir)/cp/parse.h: $(srcdir)/cp/parse.c
Index: gcc/cp/cfns.gperf
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/cfns.gperf,v
retrieving revision 1.2
diff -c -p -d -u -r1.2 cfns.gperf
--- gcc/cp/cfns.gperf 4 Apr 2000 20:46:23 -0000 1.2
+++ gcc/cp/cfns.gperf 30 Apr 2003 17:34:45 -0000
@@ -1,4 +1,20 @@
%{
+/*
+ * The standard C library functions, for feeding to gperf; the result is used
+ * by nothrow_libfn_p.
+ *
+ * [lib.res.on.exception.handling]: None of the functions from the
+ * Standard C library shall report an error by throwing an
+ * exception, unless it calls a program-supplied function that
+ * throws an exception.
+ *
+ * bsearch and qsort are omitted because they can call such functions.
+ *
+ * Cancellation points are marked either 1 (primary cancellation point)
+ * or 2 (secondary cancellation point). Taken from Section 2.9.5.2 of
+ * IEEE 1003.1 (POSIX 1) TC1.
+ */
+
#ifdef __GNUC__
__inline
#endif
@@ -6,224 +22,215 @@ static unsigned int hash PARAMS ((const
#ifdef __GNUC__
__inline
#endif
-const char * libc_name_p PARAMS ((const char *, unsigned int));
+const struct libc_name_data *libc_name_p PARAMS ((const char *, unsigned int));
%}
-# The standard C library functions, for feeding to gperf; the result is used
-# by nothrow_libfn_p.
-#
-# [lib.res.on.exception.handling]: None of the functions from the
-# Standard C library shall report an error by throwing an
-# exception, unless it calls a program-supplied function that
-# throws an exception.
-#
-# bsearch and qsort are commented out because they can call such functions.
-#
-abort
-abs
-acos
-asctime
-asin
-atan
-atan2
-atexit
-atof
-atoi
-atol
-#bsearch
-btowc
-calloc
-ceil
-clearerr
-clock
-cos
-cosh
-ctime
-difftime
-div
-exit
-exp
-fabs
-fclose
-feof
-ferror
-fflush
-fgetc
-fgetpos
-fgets
-fgetwc
-fgetws
-floor
-fmod
-fopen
-fprintf
-fputc
-fputs
-fputwc
-fputws
-fread
-free
-freopen
-frexp
-fscanf
-fseek
-fsetpos
-ftell
-fwide
-fwprintf
-fwrite
-fwscanf
-getc
-getchar
-getenv
-gets
-getwc
-getwchar
-gmtime
-isalnum
-isalpha
-iscntrl
-isdigit
-isgraph
-islower
-isprint
-ispunct
-isspace
-isupper
-iswalnum
-iswalpha
-iswcntrl
-iswctype
-iswdigit
-iswgraph
-iswlower
-iswprint
-iswpunct
-iswspace
-iswupper
-iswxdigit
-isxdigit
-labs
-ldexp
-ldiv
-localeconv
-localtime
-log
-log10
-longjmp
-malloc
-mblen
-mbrlen
-mbrtowc
-mbsinit
-mbsrtowcs
-mbstowcs
-mbtowc
-memchr
-memcmp
-memcpy
-memmove
-memset
-mktime
-modf
-perror
-pow
-printf
-putc
-putchar
-puts
-putwc
-putwchar
-#qsort
-raise
-rand
-realloc
-remove
-rename
-rewind
-scanf
-setbuf
-setlocale
-setvbuf
-signal
-sin
-sinh
-sprintf
-sqrt
-srand
-sscanf
-strcat
-strchr
-strcmp
-strcoll
-strcpy
-strcspn
-strerror
-strftime
-strlen
-strncat
-strncmp
-strncpy
-strpbrk
-strrchr
-strspn
-strstr
-strtod
-strtok
-strtol
-strtoul
-strxfrm
-swprintf
-swscanf
-system
-tan
-tanh
-time
-tmpfile
-tmpnam
-tolower
-toupper
-towctrans
-towlower
-towupper
-ungetc
-ungetwc
-vfprintf
-vfwprintf
-vprintf
-vsprintf
-vswprintf
-vwprintf
-wcrtomb
-wcscat
-wcschr
-wcscmp
-wcscoll
-wcscpy
-wcscspn
-wcsftime
-wcslen
-wcsncat
-wcsncmp
-wcsncpy
-wcspbrk
-wcsrchr
-wcsrtombs
-wcsspn
-wcsstr
-wcstod
-wcstok
-wcstol
-wcstombs
-wcstoul
-wcsxfrm
-wctob
-wctomb
-wctrans
-wctype
-wmemchr
-wmemcmp
-wmemcpy
-wmemmove
-wmemset
-wprintf
-wscanf
+struct libc_name_data { const char *name; int cancelation_point; };
+%%
+abort, 0
+abs, 0
+acos, 0
+asctime, 0
+asin, 0
+atan, 0
+atan2, 0
+atexit, 0
+atof, 0
+atoi, 0
+atol, 0
+btowc, 0
+calloc, 0
+ceil, 0
+clearerr, 0
+clock, 0
+cos, 0
+cosh, 0
+ctime, 0
+difftime, 0
+div, 0
+exit, 0
+exp, 0
+fabs, 0
+fclose, 2
+feof, 0
+ferror, 0
+fflush, 2
+fgetc, 2
+fgetpos, 2
+fgets, 2
+fgetwc, 2
+fgetws, 2
+floor, 0
+fmod, 0
+fopen, 2
+fprintf, 2
+fputc, 2
+fputs, 2
+fputwc, 2
+fputws, 2
+fread, 2
+free, 0
+freopen, 2
+frexp, 0
+fscanf, 2
+fseek, 2
+fsetpos, 2
+ftell, 2
+fwide, 0
+fwprintf, 2
+fwrite, 2
+fwscanf, 2
+getc, 2
+getchar, 2
+getenv, 0
+gets, 2
+getwc, 2
+getwchar, 2
+gmtime, 0
+isalnum, 0
+isalpha, 0
+iscntrl, 0
+isdigit, 0
+isgraph, 0
+islower, 0
+isprint, 0
+ispunct, 0
+isspace, 0
+isupper, 0
+iswalnum, 0
+iswalpha, 0
+iswcntrl, 0
+iswctype, 0
+iswdigit, 0
+iswgraph, 0
+iswlower, 0
+iswprint, 0
+iswpunct, 0
+iswspace, 0
+iswupper, 0
+iswxdigit, 0
+isxdigit, 0
+labs, 0
+ldexp, 0
+ldiv, 0
+localeconv, 0
+localtime, 0
+log, 0
+log10, 0
+longjmp, 0
+malloc, 0
+mblen, 0
+mbrlen, 0
+mbrtowc, 0
+mbsinit, 0
+mbsrtowcs, 0
+mbstowcs, 0
+mbtowc, 0
+memchr, 0
+memcmp, 0
+memcpy, 0
+memmove, 0
+memset, 0
+mktime, 0
+modf, 0
+perror, 2
+pow, 0
+printf, 2
+putc, 2
+putchar, 2
+puts, 2
+putwc, 2
+putwchar, 2
+raise, 0
+rand, 0
+realloc, 0
+remove, 2
+rename, 2
+rewind, 2
+scanf, 2
+setbuf, 0
+setlocale, 0
+setvbuf, 0
+signal, 0
+sin, 0
+sinh, 0
+sprintf, 2
+sqrt, 0
+srand, 0
+sscanf, 0
+strcat, 0
+strchr, 0
+strcmp, 0
+strcoll, 0
+strcpy, 0
+strcspn, 0
+strerror, 2
+strftime, 0
+strlen, 0
+strncat, 0
+strncmp, 0
+strncpy, 0
+strpbrk, 0
+strrchr, 0
+strspn, 0
+strstr, 0
+strtod, 0
+strtok, 0
+strtol, 0
+strtoul, 0
+strxfrm, 0
+swprintf, 0
+swscanf, 0
+system, 1
+tan, 0
+tanh, 0
+time, 0
+tmpfile, 2
+tmpnam, 2
+tolower, 0
+toupper, 0
+towctrans, 0
+towlower, 0
+towupper, 0
+ungetc, 2
+ungetwc, 2
+vfprintf, 2
+vfwprintf, 2
+vprintf, 2
+vsprintf, 0
+vswprintf, 0
+vwprintf, 2
+wcrtomb, 0
+wcscat, 0
+wcschr, 0
+wcscmp, 0
+wcscoll, 0
+wcscpy, 0
+wcscspn, 0
+wcsftime, 0
+wcslen, 0
+wcsncat, 0
+wcsncmp, 0
+wcsncpy, 0
+wcspbrk, 0
+wcsrchr, 0
+wcsrtombs, 0
+wcsspn, 0
+wcsstr, 0
+wcstod, 0
+wcstok, 0
+wcstol, 0
+wcstombs, 0
+wcstoul, 0
+wcsxfrm, 0
+wctob, 0
+wctomb, 0
+wctrans, 0
+wctype, 0
+wmemchr, 0
+wmemcmp, 0
+wmemcpy, 0
+wmemmove, 0
+wmemset, 0
+wprintf, 2
+wscanf, 2
+%%
Index: gcc/cp/cp-tree.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/cp-tree.h,v
retrieving revision 1.776.2.18
diff -c -p -d -u -r1.776.2.18 cp-tree.h
--- gcc/cp/cp-tree.h 29 Apr 2003 18:51:55 -0000 1.776.2.18
+++ gcc/cp/cp-tree.h 30 Apr 2003 17:34:46 -0000
@@ -3910,7 +3910,7 @@ extern void expand_exception_blocks PAR
extern tree build_exc_ptr PARAMS ((void));
extern tree build_throw PARAMS ((tree));
extern void mark_all_runtime_matches PARAMS ((void));
-extern int nothrow_libfn_p PARAMS ((tree));
+extern bool nothrow_libfn_p PARAMS ((tree));
extern void check_handlers PARAMS ((tree));
extern void choose_personality_routine PARAMS ((enum languages));

Index: gcc/cp/except.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/except.c,v
retrieving revision 1.149
diff -c -p -d -u -r1.149 except.c
--- gcc/cp/except.c 4 Dec 2002 20:13:01 -0000 1.149
+++ gcc/cp/except.c 30 Apr 2003 17:34:47 -0000
@@ -870,10 +870,11 @@ is_admissible_throw_operand (expr)

#include "cfns.h"

-int
+bool
nothrow_libfn_p (fn)
tree fn;
{
+ const struct libc_name_data *entry;
tree id;

if (TREE_PUBLIC (fn)
@@ -883,10 +884,16 @@ nothrow_libfn_p (fn)
/* OK */;
else
/* Can't be a C library function. */
- return 0;
+ return false;

id = DECL_ASSEMBLER_NAME (fn);
- return !!libc_name_p (IDENTIFIER_POINTER (id), IDENTIFIER_LENGTH (id));
+ entry = libc_name_p (IDENTIFIER_POINTER (id), IDENTIFIER_LENGTH (id));
+ if (!entry)
+ return false;
+ if (flag_forced_unwind_exceptions)
+ return !entry->cancelation_point;
+ else
+ return true;
}

/* Returns nonzero if an exception of type FROM will be caught by a
Index: gcc/testsuite/g++.dg/eh/forced2.C
===================================================================
RCS file: /cvs/gcc/gcc/gcc/testsuite/g++.dg/eh/forced2.C,v
retrieving revision 1.1.2.1
diff -c -p -d -u -r1.1.2.1 forced2.C
--- gcc/testsuite/g++.dg/eh/forced2.C 2 Apr 2003 07:14:29 -0000 1.1.2.1
+++ gcc/testsuite/g++.dg/eh/forced2.C 30 Apr 2003 17:34:47 -0000
@@ -25,11 +25,10 @@ force_unwind_stop (int version, _Unwind_
return _URC_NO_REASON;
}

-// Note that neither the noreturn nor the nothrow specification
-// affects forced unwinding.
+// Note that the noreturn specification doesn't affect forced unwinding.

static void __attribute__((noreturn))
-force_unwind () throw()
+force_unwind ()
{
_Unwind_Exception *exc = new _Unwind_Exception;
exc->exception_class = 0;
Index: libstdc++-v3/libsupc++/eh_personality.cc
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/libsupc++/eh_personality.cc,v
retrieving revision 1.10
diff -c -p -d -u -r1.10 eh_personality.cc
--- libstdc++-v3/libsupc++/eh_personality.cc 15 Aug 2002 18:05:41 -0000 1.10
+++ libstdc++-v3/libsupc++/eh_personality.cc 30 Apr 2003 17:34:49 -0000
@@ -124,6 +124,8 @@ get_adjusted_ptr (const std::type_info *
return false;
}

+// Return true if THROW_TYPE matches one if the filter types.
+
static bool
check_exception_spec (lsda_header_info *info, const std::type_info *throw_type,
void *thrown_ptr, _Unwind_Sword filter_value)
@@ -154,6 +156,18 @@ check_exception_spec (lsda_header_info *
}
}

+// Return true if the filter spec is empty, ie throw().
+
+static bool
+empty_exception_spec (lsda_header_info *info, _Unwind_Sword filter_value)
+{
+ const unsigned char *e = info->TType - filter_value - 1;
+ _Unwind_Word tmp;
+
+ e = read_uleb128 (e, &tmp);
+ return tmp == 0;
+}
+
// Using a different personality function name causes link failures
// when trying to mix code using different exception handling models.
#ifdef _GLIBCPP_SJLJ_EXCEPTIONS
@@ -275,7 +289,7 @@ PERSONALITY_FUNCTION (int version,
// If ip is not present in the table, call terminate. This is for
// a destructor inside a cleanup, or a library routine the compiler
// was not expecting to throw.
- found_type = (actions & _UA_FORCE_UNWIND ? found_nothing : found_terminate);
+ found_type = found_terminate;
goto do_something;

found_something:
@@ -352,9 +366,12 @@ PERSONALITY_FUNCTION (int version,
// ??? How do foreign exceptions fit in? As far as I can
// see we can't match because there's no __cxa_exception
// object to stuff bits in for __cxa_call_unexpected to use.
+ // Allow them iff the exception spec is non-empty. I.e.
+ // a throw() specification results in __unexpected.
if (throw_type
- && ! check_exception_spec (&info, throw_type, thrown_ptr,
- ar_filter))
+ ? ! check_exception_spec (&info, throw_type, thrown_ptr,
+ ar_filter)
+ : empty_exception_spec (&info, ar_filter))
{
saw_handler = true;
break;
Mark Mitchell
2003-04-30 18:39:41 UTC
Permalink
Post by Richard Henderson
The result of the original thread
http://gcc.gnu.org/ml/gcc-patches/2003-04/msg00008.html
endorsed different semantics than I assumed in my original
patch. Here I update things to match the consensus.
So, if I understand the patch:

(1) The libsupc++ bits make:

void f() throw () { read (...); }

be handled just like:

void f() throw () { throw 3; }

would.

Those bits are fine.

(2) You made the C++ front end tell the truth about C library functions,
in that -- with -fforced-unwind-exceptions -- we now treat cancellable
library functions as possibly throwing exceptions. These bits are fine.

(3) You've taught the middle end not to emit cleanups if a region does
not contain any calls to functions that may throw, even if
-fforced-unwind-exceptions is on. This is an improvement to my earlier
compile-time performance fix, and works because you now know which
library functions might be cancellable. These bits are fine.

(4) Some other black magic in except.c that I couldn't get my head
around on the first try. What do these bits do?

One key question that I'm not sure got resolved on the thread was what
happens in this case:

try {
// Something that might:
// (a) result in cancellation.
// (b) result in a foreign exception being thrown.
// (c) result in longjmp_unwind being called.
} catch (...) {
// There is no "throw;" here to rethrow.
}

Presumably (a) is the same as either (b) or (c). Which one?

And, if which of these cases are we implicitly rethrowing from the
catch, even though the user didn't write "throw;"?

Does the IA-64 ABI specify this?

The reason I ask is that:

f = fopen(...);
try {
} catch (...) {
}
fclose (f);

looks to a C++ programmer to be a perfectly safe piece of code, modulo
longjmp. I wouldn't be too unhappy if longjmp_unwind skipped closing
"f", as longjmp would do that anyhow, and longjmp_unwind is this new
weird thing that can do whatever it wants to do.

But, I'd be unhappy if foreign exceptions and/or thread cancellation did
an implicit rethrow.

Thanks,
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Zack Weinberg
2003-04-30 19:50:57 UTC
Permalink
Post by Mark Mitchell
void f() throw () { read (...); }
void f() throw () { throw 3; }
would.
Query, is there a way to write "this function may be cancelled but
otherwise will not throw an exception"? Notation like

void f() throw (cxxabi::thread_cancellation) { ... }

comes to mind.

zw
Gabriel Dos Reis
2003-04-30 20:04:02 UTC
Permalink
Zack Weinberg <***@codesourcery.com> writes:

| Mark Mitchell <***@codesourcery.com> writes:
|
| > (1) The libsupc++ bits make:
| >
| > void f() throw () { read (...); }
| >
| > be handled just like:
| >
| > void f() throw () { throw 3; }
| >
| > would.
|
| Query, is there a way to write "this function may be cancelled but
| otherwise will not throw an exception"? Notation like
|
| void f() throw (cxxabi::thread_cancellation) { ... }

hmm, assuming current C++ semantics with repesct to ES, I don't think
that helps much. Or do you have any specific extension in mind?

-- Gaby
Mark Mitchell
2003-04-30 20:02:57 UTC
Permalink
Post by Zack Weinberg
Post by Mark Mitchell
void f() throw () { read (...); }
void f() throw () { throw 3; }
would.
Query, is there a way to write "this function may be cancelled but
otherwise will not throw an exception"? Notation like
void f() throw (cxxabi::thread_cancellation) { ... }
If we gave the thread cancellation exception a binding in C++, we could
do that.

That might well be a sensible thing to do; it could always still be a
foreign exception for other languages, if they can't handle whatever
representation is used.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Richard Henderson
2003-04-30 21:04:39 UTC
Permalink
Post by Zack Weinberg
Query, is there a way to write "this function may be cancelled but
otherwise will not throw an exception"? Notation like
void f() throw (cxxabi::thread_cancellation) { ... }
No.


r~
Richard Henderson
2003-04-30 21:03:42 UTC
Permalink
Post by Mark Mitchell
(4) Some other black magic in except.c that I couldn't get my head
around on the first try. What do these bits do?
I presume you're talking about

(RNL_ALWAYS_CAUGHT): New.
(reachable_next_level): Return RNL_ALWAYS_CAUGHT for empty filter
spec and must_not_throw shadowing other catch handlers.
(reachable_handlers): With forced unwinding, only mark cleanups
live after catch(...), not throw().
(can_throw_external): Similarly wrt exceptions escaping the function.

Well, the existing code had bits to notice that must_not_throw
(the wrapper around destructor during cleanup that calls terminate)
was different from catch(...), but didn't distinguish between
throw() and catch(...). This makes that distinction, because...
Post by Mark Mitchell
One key question that I'm not sure got resolved on the thread was what
try {
// (a) result in cancellation.
// (b) result in a foreign exception being thrown.
// (c) result in longjmp_unwind being called.
} catch (...) {
// There is no "throw;" here to rethrow.
}
Presumably (a) is the same as either (b) or (c). Which one?
... I thought (a) was similar to (c) and that the catch
handler here is *not* invoked in either case.

So continuing with the first question, we want to note that with
-fforced-unwind-exceptions that while cleanups after catch(...)
are live, cleanups after throw() are not.

Oops, I've missed a case:

void foo() throw()
{
read(...)
}

will (properly) abort, but

void bar() throw()
{
try {
read(...)
} catch (...) {
}
}

won't. I'll fix that and update the patch.
Post by Mark Mitchell
And, if which of these cases are we implicitly rethrowing from the
catch, even though the user didn't write "throw;"?
We don't implicitly rethrow, because we don't allow the catch.
I've thought about this a bit, and have reservations because...
Post by Mark Mitchell
f = fopen(...);
try {
} catch (...) {
}
fclose (f);
looks to a C++ programmer to be a perfectly safe piece of code, modulo
longjmp.
... of this. It *isn't* safe unless you know that nothing in the
catch handler can throw. If we can get folks to write this as

struct closer {
FILE *f;
closer(FILE *x) : f(x) { }
~closer() { if (f) fclose (f); }
};

f = fopen(...);
{
closer c(f);
try {
} catch (...) {
}
}

I'd be much happier. Or, heaven forfend, use iostreams and
auto_ptr, which are a bit more prepared for this sort of thing
without having to define your own local classes.
Post by Mark Mitchell
Does the IA-64 ABI specify this?
No. It says things can work either way, which is a bit unsatisfying.
Post by Mark Mitchell
But, I'd be unhappy if foreign exceptions and/or thread cancellation did
an implicit rethrow.
Ok, good, we're on the same page.


r~
Mark Mitchell
2003-04-30 22:55:06 UTC
Permalink
Post by Richard Henderson
Post by Mark Mitchell
One key question that I'm not sure got resolved on the thread was what
try {
// (a) result in cancellation.
// (b) result in a foreign exception being thrown.
// (c) result in longjmp_unwind being called.
} catch (...) {
// There is no "throw;" here to rethrow.
}
Presumably (a) is the same as either (b) or (c). Which one?
... I thought (a) was similar to (c) and that the catch
handler here is *not* invoked in either case.
Oh, bummer.

I thought (a) was going to be similar to (b), not (c).

I think the key design goal should be to make correct single-threaded
code usable in a multi-threaded program, with as little modification as
possible.

(Rationale: if I have a library, obtained from somewhere, I want to be
able to use it in my multi-threaded program without having to take it
apart bit by bit to see if it's safe.)

Nathan Myers and I were just discussing this, and we agreed that running
the "catch (...)" handlers is the Right Thing -- at least for thread
cancellation and foreign exceptions. (I've got less of an opinion about
longjmp_unwind; the longmp-ness and the unwind-ness are at odds.)

If we don't let "catch (...)" handlers run, we're violating a basic C++
assumption, with the result that lots of real code will work in a
single-threaded environment, but suddenly not work in a threaded
environment.

If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled.

Therefore, I'd rather we just treat cancellation like a foreign
exception.
Post by Richard Henderson
Post by Mark Mitchell
And, if which of these cases are we implicitly rethrowing from the
catch, even though the user didn't write "throw;"?
We don't implicitly rethrow, because we don't allow the catch.
I've thought about this a bit, and have reservations because...
Post by Mark Mitchell
f = fopen(...);
try {
} catch (...) {
}
fclose (f);
looks to a C++ programmer to be a perfectly safe piece of code, modulo
longjmp.
... of this. It *isn't* safe unless you know that nothing in the
catch handler can throw. If we can get folks to write this as
struct closer {
FILE *f;
closer(FILE *x) : f(x) { }
~closer() { if (f) fclose (f); }
};
Well, yes, naturally. But people don't always do it that way, and there
are even sometimes good reasons. :-)

(This whole signals/cancellation/exception thing is really an
interesting topic; I recently co-authored a paper about how Python's
exception-model is unsafe with respect to signals, even though the
language is designed to synchronize signals in such a way that you can
throw an exception from a signal handler. This is not an interpreter
bug; it's a fundamental language design bug.)
Post by Richard Henderson
Post by Mark Mitchell
Does the IA-64 ABI specify this?
No. It says things can work either way, which is a bit unsatisfying.
Indeed.
Post by Richard Henderson
Post by Mark Mitchell
But, I'd be unhappy if foreign exceptions and/or thread cancellation did
an implicit rethrow.
Ok, good, we're on the same page.
Sort of -- it sounds like we agree about the implicit rethrow, but not
about whether or not the catch(...) clauses are actually entered or not.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Richard Henderson
2003-04-30 23:02:39 UTC
Permalink
Post by Mark Mitchell
If we don't let "catch (...)" handlers run, we're violating a basic C++
assumption, with the result that lots of real code will work in a
single-threaded environment, but suddenly not work in a threaded
environment.
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled.
Therefore, I'd rather we just treat cancellation like a foreign
exception.
Ug. I guess I'll stop what I'm doing, since this directly
affects the code I'm writing.

Um, ok, do we *really* have consensus this time? Jason, Uli?


r~
Ulrich Drepper
2003-04-30 23:16:34 UTC
Permalink
[...]
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled.
No, this is not acceptable. Once cancelled a thread cannot be allowed
to prevent this. Cancellation changes the internal state of the thread,
it cannot continue running. The POSIX standard is clear on that, if a
thread leaves the cleanup handler in some way the behavior is undefined.
The rethrow is mandatory in case the catch blocks are expected to run.
There is no discussion possible, this is a fact.

And your assessment that single-threaded and multi-threaded apps behave
differently is wrong, too. There is no way a single threaded app can
get cancelled. Normal termination, via exceptions or not, is different
from cancellation.

If catch blocks are executed they must be converted in in "finally"
blocks with automatic rethrow. I'm not sure whether catch blocks should
be handled, my gut feeling is no.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Nathan Myers
2003-04-30 23:50:53 UTC
Permalink
Post by Ulrich Drepper
[...]
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled.
No, this is not acceptable. Once cancelled a thread cannot be allowed
to prevent this. Cancellation changes the internal state of the thread,
it cannot continue running. The POSIX standard is clear on that, if a
thread leaves the cleanup handler in some way the behavior is undefined.
The rethrow is mandatory in case the catch blocks are expected to run.
There is no discussion possible, this is a fact.
Any thread can prevent cancellation by putting "while (true);" in
a destructor. Once control leaves the catch block, the thread can't
do any more externally visible work anyway, because that would touch
another cancellation point, which (I assume) would throw again.

If and only if throwing again can't be made to work, then you may
declare that a catch(...) clause not rethrowing a cancellation
exception results in undefined behavior. It is already considered
poor practice for a library not to rethrow unidentified exceptions.
Normally, swallowing unknown exceptions is done only in a main
program, or if in a library, then only because the library is meant
to be called from a language that cannot handle exceptions. Neither
consideration applies here.
Post by Ulrich Drepper
And your assessment that single-threaded and multi-threaded apps behave
differently is wrong, too. There is no way a single threaded app can
get cancelled. Normal termination, via exceptions or not, is different
from cancellation.
No. Exception-safe code is also (almost always) thread-safe as well,
and users reasonably expect exception-safe library code to be usable
in a thread. You cannot announce that exception-safe code that has
been in long use is suddenly not thread-safe any more just because
you chose to break the language's exception semantics.
Post by Ulrich Drepper
If catch blocks are executed they must be converted in in "finally"
blocks with automatic rethrow. I'm not sure whether catch blocks should
be handled, my gut feeling is no.
There is no need to convert blocks to automatically rethrow. Coders
can take responsibility for their own code.

Nathan Myers
***@cantrip.org
Ulrich Drepper
2003-05-01 00:05:57 UTC
Permalink
Post by Nathan Myers
There is no need to convert blocks to automatically rethrow. Coders
can take responsibility for their own code.
That's crap. It's not you who gets the blame. Anything but an
automatic handling of the rethrow means that there will be people not
rethrowing the code which in turn means that sooner or later they will
run into problems. Not only will this be a nightmare to debug, but it
also just mean that those lusers demand that it is possible to write
such code since C++ exceptions allow it. Your "good C++ code measure"
is nothing but a recommendation.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Richard Henderson
2003-05-01 00:17:59 UTC
Permalink
Post by Nathan Myers
Any thread can prevent cancellation by putting "while (true);" in
a destructor. Once control leaves the catch block, the thread can't
do any more externally visible work anyway, because that would touch
another cancellation point, which (I assume) would throw again.
Yeah, but you have to work at it to not cancel the thread
(via exit or longjmp or infinite loop) from a destructor,
whereas it's easy to forget to rethrow inside a catch(...).

Thus, IMO destructors should be preferred over catch(...)
for *all* cleanup operations, not just for this cancellation
question.


r~
Mark Mitchell
2003-05-01 00:39:02 UTC
Permalink
Post by Richard Henderson
Post by Nathan Myers
Any thread can prevent cancellation by putting "while (true);" in
a destructor. Once control leaves the catch block, the thread can't
do any more externally visible work anyway, because that would touch
another cancellation point, which (I assume) would throw again.
Yeah, but you have to work at it to not cancel the thread
(via exit or longjmp or infinite loop) from a destructor,
whereas it's easy to forget to rethrow inside a catch(...).
Thus, IMO destructors should be preferred over catch(...)
for *all* cleanup operations, not just for this cancellation
question.
I agree. Users should be encouraged to use destructors;
resource-acquisition-as-initialization is a good C++ idiom.

But we shouldn't go silently breaking millions of lines of until-now
exception-safe ISO-conformant code that happens to use "catch(...)"
rather than destructors. I'd ever so much rather debug a
why-didn't-my-thread-go-away bug than a
why-is-my-data-in-an-inconsistent-state bug.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Richard Henderson
2003-05-01 02:23:25 UTC
Permalink
Post by Mark Mitchell
But we shouldn't go silently breaking millions of lines of until-now
exception-safe ISO-conformant code that happens to use "catch(...)"
rather than destructors. I'd ever so much rather debug a
why-didn't-my-thread-go-away bug than a
why-is-my-data-in-an-inconsistent-state bug.
At this rate we will simply never come to agreement.

(1) I say we cannot enter catch(...) without adding code to do
implicit rethrow because it's *very* likely that your
"exception-safe ISO-conformant code" does not include a
rethrow and is completely unsafe for thread cancellation.

(2) You say we must enter catch(...) because the idiom is so
common that millions of lines will break. Further, you
don't like implicit rethrow.

(3) If we simply abort when crossing catch(...) we will most
definitely cause thread cancellation to be completely
worthless to everyone.

So what are we to do? The best I can offer in good conscience
is adding the implicit rethrow.


r~
Gabriel Dos Reis
2003-05-02 13:19:00 UTC
Permalink
Mark Mitchell <***@codesourcery.com> writes:

[...]

| But we shouldn't go silently breaking millions of lines of until-now
| exception-safe ISO-conformant code that happens to use "catch(...)"
| rather than destructors. I'd ever so much rather debug a
| why-didn't-my-thread-go-away bug than a
| why-is-my-data-in-an-inconsistent-state bug.

strongly seconded. If we choose to map thread cancellation into
exception, then it rather obeys basic EH rules -- even though RAII is
certainly a good practice.

-- Gaby
Fergus Henderson
2003-05-02 15:20:57 UTC
Permalink
Post by Gabriel Dos Reis
[...]
| But we shouldn't go silently breaking millions of lines of until-now
| exception-safe ISO-conformant code that happens to use "catch(...)"
| rather than destructors. I'd ever so much rather debug a
| why-didn't-my-thread-go-away bug than a
| why-is-my-data-in-an-inconsistent-state bug.
strongly seconded.
I agree that ignoring "catch(...)" handlers for cancels is not going to fly.
I agree about which bug I'd prefer to debug.

But my intuition is that I think that very few of those million lines
of until-now exception-safe ISO-conformant code would break from an
implicit rethrow if/when a cancel got swallowed -- I think a lot more
would break from an implicit terminate() in the same situation.
--
Fergus Henderson <***@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Mike Harrold
2003-05-02 15:31:54 UTC
Permalink
Post by Fergus Henderson
Post by Gabriel Dos Reis
[...]
| But we shouldn't go silently breaking millions of lines of until-now
| exception-safe ISO-conformant code that happens to use "catch(...)"
| rather than destructors. I'd ever so much rather debug a
| why-didn't-my-thread-go-away bug than a
| why-is-my-data-in-an-inconsistent-state bug.
strongly seconded.
I agree that ignoring "catch(...)" handlers for cancels is not going to fly.
I agree about which bug I'd prefer to debug.
But my intuition is that I think that very few of those million lines
of until-now exception-safe ISO-conformant code would break from an
implicit rethrow if/when a cancel got swallowed -- I think a lot more
would break from an implicit terminate() in the same situation.
But only if the implicit rethrow happens when a cancellation exception has
been caught in the catch (...) block. Other exceptions _must_ be allowed
to fall off the end of the block...

Regards,

/Mike
Gabriel Dos Reis
2003-05-02 16:08:48 UTC
Permalink
Mike Harrold <***@cas.org> writes:

| But only if the implicit rethrow happens when a cancellation exception has
| been caught in the catch (...) block. Other exceptions _must_ be allowed
| to fall off the end of the block...

I believe it is important to remember that C++ is also a "glue"
language meaning that it is also used to pack different applications
together. One implication is that it ought to be possible for a C++
program to catch exceptions from foreign languages. I cannot imagine
that the notion of "ghost" exceptions will make it.

-- Gaby
Nathan Myers
2003-05-02 16:09:20 UTC
Permalink
Post by Fergus Henderson
| I'd ever so much rather debug a
| why-didn't-my-thread-go-away bug than a
| why-is-my-data-in-an-inconsistent-state bug.
strongly seconded.
I agree that ignoring "catch(...)" handlers for cancels is not going to fly.
I agree about which bug I'd prefer to debug.
But my intuition is that I think that very few of those million lines
of until-now exception-safe ISO-conformant code would break from an
implicit rethrow if/when a cancel got swallowed -- I think a lot more
would break from an implicit terminate() in the same situation.
Reams of code would be broken in either case. How much in each
is debatable, and moot.

If code is to be broken, it should be broken as loudly as possible.
At compile time, if possible. Automatically re-throwing violates
that principle.

It's best just not to break the code at all, and that is also easily
implementable. Apparently all it needs is to give the cancelled
thread defined semantics for all plausible operations. That also
ought to be easy.

Nobody has posted any reason why the state of a cancelled thread
should be so corrupted, beyond "read the code", without any details
even of where this code is. Can somebody please post the code or
a URL, and an explanation of why it must be so?

Thus far, nobody has more than hinted at any rationally sound reason
for breaking all exception-safe libraries used in thread contexts.
We really should see the details before this goes any farther.

Nathan Myers
***@cantrip.org
Jason Merrill
2003-04-30 23:12:33 UTC
Permalink
Post by Mark Mitchell
Post by Richard Henderson
Post by Mark Mitchell
One key question that I'm not sure got resolved on the thread was what
try {
// (a) result in cancellation.
// (b) result in a foreign exception being thrown.
// (c) result in longjmp_unwind being called.
} catch (...) {
// There is no "throw;" here to rethrow.
}
Presumably (a) is the same as either (b) or (c). Which one?
... I thought (a) was similar to (c) and that the catch
handler here is *not* invoked in either case.
I agree.
Post by Mark Mitchell
Oh, bummer.
I thought (a) was going to be similar to (b), not (c).
To me, the (a) condition is much more like the (c) condition. That was
also the consensus at the ABI committee meetings. I don't remember anyone
disagreeing that cancellation and longjmp_unwind would use the same code.
Post by Mark Mitchell
I think the key design goal should be to make correct single-threaded
code usable in a multi-threaded program, with as little modification as
possible.
As little as possible, but no less. :)
Post by Mark Mitchell
(Rationale: if I have a library, obtained from somewhere, I want to be
able to use it in my multi-threaded program without having to take it
apart bit by bit to see if it's safe.)
Nathan Myers and I were just discussing this, and we agreed that running
the "catch (...)" handlers is the Right Thing -- at least for thread
cancellation and foreign exceptions. (I've got less of an opinion about
longjmp_unwind; the longmp-ness and the unwind-ness are at odds.)
If we don't let "catch (...)" handlers run, we're violating a basic C++
assumption, with the result that lots of real code will work in a
single-threaded environment, but suddenly not work in a threaded
environment.
The problem is that catch(...) is overloaded in C++. It's used both for
code that wants to write a cleanup inline and rethrow and for code that
wants to trap all exceptions.

For cancellation and longjmp_unwind, we would like to be able to support
the first use, but we cannot support the second. There is no good answer
to this question, which is why it's fudged in the ABI; basically, the ABI
says that if a compiler is clever enough to distinguish the cases, it can,
but it's not required.

The simple solution is to give up on handling catch(...) at all in forced
unwind situations and tell people that they should use a local object
cleanup instead.

If C++ had try/finally, that would be the obviously correct way to write
the first use. Unfortunately, it doesn't.
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler.
It could, but that would be a bug in the program. A cancellation request,
when acted on, is defined to be equivalent to pthread_exit, and there is
nothing to suggest that pthread_exit sometimes doesn't actually terminate
the thread.

Jason
Mark Mitchell
2003-05-01 00:29:20 UTC
Permalink
(I've replying to both Jason and Ulrich in this message.)
Post by Jason Merrill
To me, the (a) condition is much more like the (c) condition. That was
also the consensus at the ABI committee meetings. I don't remember anyone
disagreeing that cancellation and longjmp_unwind would use the same code.
Heck, I don't even remember this being discussed, so you're way ahead of
me! :-)
Post by Jason Merrill
For cancellation and longjmp_unwind, we would like to be able to support
the first use, but we cannot support the second. There is no good answer
to this question, which is why it's fudged in the ABI; basically, the ABI
says that if a compiler is clever enough to distinguish the cases, it can,
but it's not required.
No, this is not acceptable. Once cancelled a thread cannot be allowed
to prevent this. Cancellation changes the internal state of the thread,
it cannot continue running. The POSIX standard is clear on that, if a
thread leaves the cleanup handler in some way the behavior is undefined.
The rethrow is mandatory in case the catch blocks are expected to run.
There is no discussion possible, this is a fact.
This whole idea -- that we unwind the stack running code when a thread
is cancelled -- is outside POSIX.

And all "undefined behavior" means is that we can choose what it does,
and that users cannot rely on that behavior being portable to other
POSIX systems.
Post by Jason Merrill
The simple solution is to give up on handling catch(...) at all in forced
unwind situations and tell people that they should use a local object
cleanup instead.
If it is really true that we cannot enter "catch(...)" blocks for some
reason (which I do not yet believe, despite Ulrich's usual inimitable
expression of confidence), then running into a "catch(...)" block while
unwinding should result in a call to std::terminate.

Unwinding past a "catch (...)" clause without running it is not helpful
to people. It's fine to tell people to use destructors, but we're
silently changing the meaning of tons of existing code, all of which
will now have to be audited for safety.

In practice, this is the kind of thing that causes people not to use
C++. A life-critical system can restart itself if an abort occurs --
they're designed for that -- but silently skipping a cleanup can kill
people. I'd have to advise customers never to use deferred thread
cancellation with C++ libraries, without doing an audit of every
catch(...) clause in the library.
Post by Jason Merrill
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler.
It could, but that would be a bug in the program. A cancellation request,
when acted on, is defined to be equivalent to pthread_exit, and there is
nothing to suggest that pthread_exit sometimes doesn't actually terminate
the thread.
I'm not sure why this would be a bug -- it's not even undefined
behavior. But, if it is a bug, then a "catch(...)" that doesn't rethrow
would be the same bug. There's no harm in providing the user a
C++-specific way to make the same mistake.

There's nothing that says that a destructor must return, or cannot
contain arbitrary code. The distinction that's being made between
"catch (...)" clauses and destructors is entirely artificial.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Ulrich Drepper
2003-05-01 00:39:42 UTC
Permalink
Post by Mark Mitchell
This whole idea -- that we unwind the stack running code when a thread
is cancelled -- is outside POSIX.
You miss the point. Doing something outside POSIX is one thing.
Requesting something which isn't possible is the other. It's not just
not defined behavior that cancellations cannot be interrupted, it's
impossible in the implementations. That's the point. Bogus
requirements from the C++ integration cannot overwrite restrictions the
implementations have.
Post by Mark Mitchell
And all "undefined behavior" means is that we can choose what it does,
No.
Post by Mark Mitchell
If it is really true that we cannot enter "catch(...)" blocks for some
reason (which I do not yet believe, despite Ulrich's usual inimitable
expression of confidence), then running into a "catch(...)" block while
unwinding should result in a call to std::terminate.
I never said that catch blocks cannot be entered. I only said that the
result must be a rethrow.
Post by Mark Mitchell
I'm not sure why this would be a bug -- it's not even undefined
behavior. But, if it is a bug, then a "catch(...)" that doesn't rethrow
would be the same bug. There's no harm in providing the user a
C++-specific way to make the same mistake.
You have the same problem as Nathan.

In C a programmers has to explicitly add code to violate the rules and
it is a burden.

If rethrows are not done automatically the ommission of one piece of
code which is not required leads to desaster.
Post by Mark Mitchell
There's nothing that says that a destructor must return, or cannot
contain arbitrary code.
Of course they cannot contain arbitrary code. You're not allowed to
call longjmp(9 etc in destructors.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Mark Mitchell
2003-05-01 00:57:58 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Mark Mitchell
This whole idea -- that we unwind the stack running code when a thread
is cancelled -- is outside POSIX.
You miss the point. Doing something outside POSIX is one thing.
Requesting something which isn't possible is the other. It's not just
not defined behavior that cancellations cannot be interrupted, it's
impossible in the implementations. That's the point. Bogus
requirements from the C++ integration cannot overwrite restrictions the
implementations have.
You've not explained how falling off the end of a catch handler is
impossible, but running a destructor that doesn't return, is possible.
Post by Mark Mitchell
I'm not sure why this would be a bug -- it's not even undefined
behavior. But, if it is a bug, then a "catch(...)" that doesn't rethrow
would be the same bug. There's no harm in providing the user a
C++-specific way to make the same mistake.
You have the same problem as Nathan.
In C a programmers has to explicitly add code to violate the rules and
it is a burden.
If rethrows are not done automatically the ommission of one piece of
code which is not required leads to desaster.
Skipping over a "catch(...)" block is a bad idea.

Inserting an implicit rethrow is a bad idea.

A compromise position is to call std::terminate for now. At some later
date, when a consensus has arisen, after consulting the POSIX and C++
committees, we can choose what to do. In the meantime, programmers will
be incentivized to convert to the less-controversial destructor-based
approach.
Post by Mark Mitchell
There's nothing that says that a destructor must return, or cannot
contain arbitrary code.
Of course they cannot contain arbitrary code. You're not allowed to
call longjmp(9 etc in destructors.
You're nitpicking; you know what I meant.

(And besides, you can call longjmp. There are restrictions on where the
corresponding setjmp can be.)
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Ulrich Drepper
2003-05-01 01:02:37 UTC
Permalink
Post by Mark Mitchell
You've not explained how falling off the end of a catch handler is
impossible, but running a destructor that doesn't return, is possible.
I did. The state of the thread changes. It's not a normal thread
anymore, it's a terminating thread. As such it cannot work normally
anymore, it will behave badly or work not all anymore if used outside
the cleanup handlers. It might hang or bring the entire process down.
Post by Mark Mitchell
Skipping over a "catch(...)" block is a bad idea.
Inserting an implicit rethrow is a bad idea.
I don't see how the latter can be bad. That's what is supposed to
happen anyway.
Post by Mark Mitchell
A compromise position is to call std::terminate for now.
For all catch blocks or only catch(...)?

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Mark Mitchell
2003-05-01 01:26:16 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Mark Mitchell
You've not explained how falling off the end of a catch handler is
impossible, but running a destructor that doesn't return, is possible.
I did. The state of the thread changes. It's not a normal thread
anymore, it's a terminating thread. As such it cannot work normally
anymore, it will behave badly or work not all anymore if used outside
the cleanup handlers. It might hang or bring the entire process down.
You still haven't explained what might happen outside the catch-clause
that cannot happen inside the catch-clause or inside a destructor.
Post by Mark Mitchell
Skipping over a "catch(...)" block is a bad idea.
Inserting an implicit rethrow is a bad idea.
I don't see how the latter can be bad. That's what is supposed to
happen anyway.
Whether that is supposed to happen or not depends on whether the user
wrote "throw;" in the catch-clause. It's perfectly reasonable to fall
off the end of catch-clauses; that's one way of saying "try this
operation but keep going if it fails."
Post by Mark Mitchell
A compromise position is to call std::terminate for now.
For all catch blocks or only catch(...)?
I'd be happy either way, in that it would give us time to get this right
before committing to the semantics.

There's no way that we'll ever be able to run code inside a catch clause
other than "catch (...)"; the exception thrown is not of the right type,
so there's no way to bind the handler parameter. Therefore, I think
that if we're going to unwind the stack, it makes sense to skip over
catch-clauses, other than "catch (...)" clauses.

(It's also vital that cleanups registered with pthread_cleanup_push be
run in LIFO order with respect to C++ destructors and catch-clauses
during stack-unwinding. That's easy to do if you're going to unwind the
stack (by having the object created by pthread_cleanup_push have a
destructor), but it's not implemented in the version of glibc that comes
with Red Hat 9.)
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Ulrich Drepper
2003-05-01 01:51:46 UTC
Permalink
Post by Mark Mitchell
You still haven't explained what might happen outside the catch-clause
that cannot happen inside the catch-clause or inside a destructor.
It is unspecified. That's the whole point.
Post by Mark Mitchell
Post by Ulrich Drepper
I don't see how the latter can be bad. That's what is supposed to
happen anyway.
Whether that is supposed to happen or not depends on whether the user
wrote "throw;" in the catch-clause. It's perfectly reasonable to fall
off the end of catch-clauses; that's one way of saying "try this
operation but keep going if it fails."
Then calling the code in catch() is wrong for cancellation.
Post by Mark Mitchell
There's no way that we'll ever be able to run code inside a catch clause
other than "catch (...)"; the exception thrown is not of the right type,
so there's no way to bind the handler parameter. Therefore, I think
that if we're going to unwind the stack, it makes sense to skip over
catch-clauses, other than "catch (...)" clauses.
This assumes that the cancellation "exception" is not derived from
exception. If this is the general concensus it's perfectly fine with me.
Post by Mark Mitchell
(It's also vital that cleanups registered with pthread_cleanup_push be
run in LIFO order with respect to C++ destructors and catch-clauses
during stack-unwinding. That's easy to do if you're going to unwind the
stack (by having the object created by pthread_cleanup_push have a
destructor), but it's not implemented in the version of glibc that comes
with Red Hat 9.)
No it's not but that doesn't matter. There are other things which
require code to be recompiled to take advantage of the new cancellation.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Mark Mitchell
2003-05-01 02:22:30 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Mark Mitchell
You still haven't explained what might happen outside the catch-clause
that cannot happen inside the catch-clause or inside a destructor.
It is unspecified. That's the whole point.
I know that POSIX doesn't endorse this particular behavior, and that
code that relies on it is non-portable -- but so is unwinding the
stack. You could even reasonably argue that unwinding the stack is a
violation of the POSIX standard; it doesn't say that you do that.

You've said that falling off the end of a catch clause might cause all
kinds of bad things to happen, like process crashes. I'm trying to find
out why. And why that isn't a problem in destructors.

I'm not trying to find out why that could be true on some hypothetical
POSIX system; I want to find out why GNU/Linux, with the GNU C library,
cannot fall off the end of a catch-clause, but can safely execute code
in a destructor.
Post by Mark Mitchell
Whether that is supposed to happen or not depends on whether the user
wrote "throw;" in the catch-clause. It's perfectly reasonable to fall
off the end of catch-clauses; that's one way of saying "try this
operation but keep going if it fails."
Then calling the code in catch() is wrong for cancellation.
You can't know that apriori.

The C++ programming language does not give you the information you want;
it does not distinguish "catch" indicating "this is where important
resources are cleaned up" and "catch" indicating "I want to keep going
if something bad happens." Sometimes "catch" indicates both of these
things, or one or the other depending on some condition.

Since the language doesn't tell you that, you have to trust the
programmer. It's wrong to try to either skip the clean up or rethrow
from the end of the clean up; either will drastically change the
semantics of programs.
Post by Mark Mitchell
There's no way that we'll ever be able to run code inside a catch clause
other than "catch (...)"; the exception thrown is not of the right type,
so there's no way to bind the handler parameter. Therefore, I think
that if we're going to unwind the stack, it makes sense to skip over
catch-clauses, other than "catch (...)" clauses.
This assumes that the cancellation "exception" is not derived from
exception. If this is the general concensus it's perfectly fine with me.
Or, rather, it assumes that it's not derived from any C++ type
whatsoever.

If we change that assumption later, that's OK; some programs that used
to abort will now handle the exception.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Ulrich Drepper
2003-05-01 02:46:40 UTC
Permalink
Post by Mark Mitchell
I know that POSIX doesn't endorse this particular behavior, and that
code that relies on it is non-portable -- but so is unwinding the
stack. You could even reasonably argue that unwinding the stack is a
violation of the POSIX standard; it doesn't say that you do that.
You still don't get it. This is not about violating POSIX or not. This
is about implementation (not specifications) relying on the program
never to return from the dead. A very reasonable assumption. Once
canceled the only way out is to die after all cleanup handlers are
called. Entire implementations of thread libraries depend on this and
this cannot be changed.
Post by Mark Mitchell
You've said that falling off the end of a catch clause might cause all
kinds of bad things to happen, like process crashes. I'm trying to find
out why. And why that isn't a problem in destructors.
And for the third or fourth time: because the state of the thread
changed. Certain things cannot be done once the state is cancelled.
These are no arbitrary limitations, they arise from actual implementations.

Listing explicitly things which can go wrong is nonsense. After the
next change to the thread library the results can be different. That's
the whole point behind calling something unspecified.
Post by Mark Mitchell
The C++ programming language does not give you the information you want;
it does not distinguish "catch" indicating "this is where important
resources are cleaned up" and "catch" indicating "I want to keep going
if something bad happens." Sometimes "catch" indicates both of these
things, or one or the other depending on some condition.
This all the more shows that executing catch() code is inadequate or
even wrong.
Post by Mark Mitchell
Since the language doesn't tell you that, you have to trust the
programmer.
Not if the default is to write code which is only causing problems.
Again, in C one has to explicitly violate the rules by adding code. By
not rethrowing automatically the default code, used in lots of code,
violates the rules.
Post by Mark Mitchell
It's wrong to try to either skip the clean up or rethrow
from the end of the clean up; either will drastically change the
semantics of programs.
The semantics is changed anyway since C++ destructors haven't been
executed so far. The programs which needed this used the C functions so
far.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Mark Mitchell
2003-05-01 04:29:30 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Mark Mitchell
I know that POSIX doesn't endorse this particular behavior, and that
code that relies on it is non-portable -- but so is unwinding the
stack. You could even reasonably argue that unwinding the stack is a
violation of the POSIX standard; it doesn't say that you do that.
You still don't get it. This is not about violating POSIX or not. This
is about implementation (not specifications) relying on the program
never to return from the dead. A very reasonable assumption. Once
canceled the only way out is to die after all cleanup handlers are
called. Entire implementations of thread libraries depend on this and
this cannot be changed.
I think we've now got (at least a temporary) resolution to this problem,
in the form of my suggestion as augmented with Jason's very clever idea.

You seem to be saying that:

(1) It is safe to unwind the stack, calling destructors and running code
inside catch-clauses. Furthermore, it is safe for that code to access
variables in the stack frames which contain it, call other functions
(possibly making the stack bigger than it was before), make system
calls, and so forth, with the possible exceptions of calling
pthread_exit (forbidden by POSIX), calling longjmp, and other similar
corner-cases.

(2) It is, however, unsafe to execute code that is not within a
destructor or a catch-clause.

I know that it should be easy even for someone of my admittedly limited
intelligence to figure out why this should be the case, but, as you've
pointed out, despite your multiple explanations, I'm just not
understanding.

Maybe you could point me at the source code for one of these thread
library implementations where (1) holds, but (2) does not?

I can't even figure out why this wouldn't work:

/* This is the function that is first called in the new thread. */

void __thread_start (void *(*fp)(void *), void* arg)
{
void* r;
r = (*fp)(arg);
if (__thread_has_been_cancelled ()) {
/* This thread was cancelled, but returned normally.

Perhaps, during the stack-unwinding for the cancellation
exception, a catch-clause did not rethrow the exception.

Since we got here, the stack is fully unwound and all
cleanups registered with pthread_push_cleanup have been run.

If there is some data structure that was created when
cancellation occurred, get rid of it now. For example,
maybe cancellation was implemented by a signal, and since
the stack unwinding didn't ever terminate, there's some
signal-stack lying around that we need to clean up. Of
course, this could have been handled when the exception
object was destroyed via Jason's idea... */
}
return r;
}
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Ulrich Drepper
2003-05-01 05:34:38 UTC
Permalink
Post by Mark Mitchell
Maybe you could point me at the source code for one of these thread
library implementations where (1) holds, but (2) does not?
Just look at the code.

Cancellation is idempotent. Therefore a flag is set so that
cancellation cannot happen in cancellation handlers. If the program
continues cancellation won't be possible anymore and the program can get
stuck. pthread_exit() doesn't work anymore is one other thing. In
other implementation (maybe even in LinuxThreads) you get endless loops
of calling cleanup handlers.

There are countless ways this can go wrong. The requirements POSIX sets
on cancellationare so that it has to be stateful.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Mark Mitchell
2003-05-01 15:27:47 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Mark Mitchell
Maybe you could point me at the source code for one of these thread
library implementations where (1) holds, but (2) does not?
Just look at the code.
Cancellation is idempotent. Therefore a flag is set so that
cancellation cannot happen in cancellation handlers. If the program
continues cancellation won't be possible anymore and the program can get
stuck. pthread_exit() doesn't work anymore is one other thing. In
other implementation (maybe even in LinuxThreads) you get endless loops
of calling cleanup handlers.
Cancellation doesn't occur again, even if you call a cancellation point.
You can't call pthread_exit.

A program calling pthread_join may wait arbitrarily long for the
cancelled thread to exit.

That's POSIX threads for you. It makes no difference whether or not
these things happen from within a pthread_cleanup_push function that
happens to run for a long time, a pthread_key_create destructor that
happens to run for a long time, or a catch handler that chooses not to
rethrow the exception.

If the catch handler chooses not to rethrow the exception, one of a few
things will happen:

(1) The process dies.

(2) The thread runs forever.

(3) The thread misbehaves by calling pthread_exit, or some other
function it's no longer allowed to call.

(4) At some point the thread will return from the main thread function.

Only (4) requires any special handling in the threads library, and the
special handling required is not complex: run pthread_key_create
destructors that have not yet been run, and terminate the thread.

It's perfectly reasonable to argue that catch-clauses should, by
default, rethrow. Likewise, it's reasonable to argue that "break"
should be the default after a case label, and that "extern void f()"
should mean "extern void f(void)" rather than "extern void f(...)".

C++ programmers understand that "catch (...)" should usually rethrow in
the same way that C programmers know that "break" should usually be
inserted before a case label. As with fall-through case-statements,
however, it is sometimes reasonable just to catch the exception.

--

The most useful things for pthread cancellation to do in C++ is to throw
an ordinary exception of some type "pthread_cancellation" with no
special semantics whatsoever.

The reason the current compromise is acceptable is that it provides a
forward-compatible subset of that functionality:

- You can't catch that particular exception type, but you can catch it
with catch (...). If the exception later gets the type
pthread_cancellation, then all of your catch (...) handlers will still
catch it, but you'll also be able to catch just thread-cancellation
exceptions.

- You can't choose not to rethrow the exception, nor can you choose to
rethrow an exception of a different type -- but in these circumstances
your process will die; therefore, when the more useful semantics are
provided all processes that did not die before will still not die.

This compromise will not do what C++ programmers want, and it will still
prevent them from using many exception-safe libraries in multi-threaded
programs, but some libraries may work, and at least the failure mode for
the others will be relatively obvious.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Fergus Henderson
2003-05-01 03:35:34 UTC
Permalink
Post by Mark Mitchell
A compromise position is to call std::terminate for now. At some later
date, when a consensus has arisen, after consulting the POSIX and C++
committees, we can choose what to do.
So long as std::terminate() is called only if/when a catch(...) handler
finishes without rethrowing the cancellation exception, I think that is
a reasonable *interim* solution.
--
Fergus Henderson <***@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Gabriel Dos Reis
2003-05-02 13:24:00 UTC
Permalink
Mark Mitchell <***@codesourcery.com> writes:

[...]

| A compromise position is to call std::terminate for now.

Agreed. C++ programmers are used to this behaviour with respect tp
EH.

-- Gaby
Jason Merrill
2003-05-01 02:24:24 UTC
Permalink
Post by Mark Mitchell
If it is really true that we cannot enter "catch(...)" blocks for some
reason (which I do not yet believe, despite Ulrich's usual inimitable
expression of confidence), then running into a "catch(...)" block while
unwinding should result in a call to std::terminate.
This makes sense to me.

Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case. So if the
catch(...) block rethrows, all is good, but if we exit the catch block some
other way, we try to clean up the exception, which calls terminate.

Jason
Richard Henderson
2003-05-01 02:40:59 UTC
Permalink
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case. So if the
catch(...) block rethrows, all is good, but if we exit the catch block some
other way, we try to clean up the exception, which calls terminate.
Oh, cool. This is a *much* nicer idea than I was contemplating.
Yes, I can agree to this. Uli, does this satisfy?


r~
Ulrich Drepper
2003-05-01 02:50:39 UTC
Permalink
Post by Richard Henderson
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case. So if the
catch(...) block rethrows, all is good, but if we exit the catch block some
other way, we try to clean up the exception, which calls terminate.
Oh, cool. This is a *much* nicer idea than I was contemplating.
Yes, I can agree to this. Uli, does this satisfy?
This means the object which is passed to the catch() code has a
destructor which is if necessary executed and can be prevented to be
called by explicitly rethrowing? This sounds excellent.

- --
- --------------. ,-. 444 Castro Street
Ulrich Drepper \ ,-----------------' \ Mountain View, CA 94041 USA
Red Hat `--' drepper at redhat.com `---------------------------
Nathan Myers
2003-05-01 04:52:19 UTC
Permalink
Post by Ulrich Drepper
Post by Richard Henderson
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as
the destructor for the exception object in the forced unwind case.
So if the catch(...) block rethrows, all is good, but if we exit the
catch block some other way, we try to clean up the exception, which
calls terminate.
Oh, cool. This is a *much* nicer idea than I was contemplating.
Yes, I can agree to this. Uli, does this satisfy?
This means the object which is passed to the catch() code has a
destructor which is if necessary executed and can be prevented to be
called by explicitly rethrowing? This sounds excellent.
This alternative is almost tolerable. I will explain below.

First, though, we should run the choices by the ISO C++ committee
and the Boost.org list. The latter, particularly, have done a long
and deep analysis of C++ thread-library semantics, and have been
distributing for years a much-admired C++ binding to POSIX threads.
Large systems have been constructed using this library, leaving both
a wealth of experience and a huge mass of existing good code.

The Boost thread library implements delayed cancellation using a
distinguished exception, and a "cancelled" flag that is checked at
(many) cancellation points. (The Boost implementation is unable to
instrument many of the Posix cancellation points, such as read()
and write(), so its threads don't necessarily die as early as they
should.) User code routinely catches this exception and re-throws it.
Sometimes, though, the exception is caught and discarded, without
grave consequences.

If a cancellation exception is caught and discarded, then at the next
cancellation point another is thrown. This has turned out to be
entirely practical. Since the "change of state" that Ulrich speaks
of amounts to a boolean flag being set, to be checked at cancellation
points, continuing out of a catch block has no more dire consequence
than delaying the death of the thread (which executing the destructors
does anyhow). Since destructors may execute arbitrary code, to say
that the thread state is somehow incapable of executing the code after
the catch block seems to call for more support than a bald assertion,
particularly given the Boost experience.

Because the Boost.org thread library is almost a shoo-in candidate for
adoption as part of the next Standard C++ Library, experience with it,
and its designers and implementers, must not be taken lightly. It
would be a grave error to implement runtime semantics fundamentally
incompatible with the Boost C++ threads library cancellation policy.

Now, about the consequences of registering terminate() as the
destructor for the cancellation object... First, the essential
principle to observe in analyzing choices is that code compiled for
use by a thread has to work the same way in a non-threaded program,
such as a unit-test harness. Nobody can afford to maintain libraries
that must behave differently when called by a thread. Few can afford
to compile libraries differently when they might be linked with a
threaded program.

Many of the requirements observed by exception-safe code are
fortuitously very similar to those for thread-safe code, so it is
common practice to write libraries that are both exception-safe and
thread-safe. Since exception-handling code is usually the least
well-exercised part of any system, it is critical to keep it simple
and transparent. Accommodating two different behaviors eliminates
transparency.

What makes the proposal almost tolerable is that it is considered
best practice under almost all circumstances to rethrow unidentified
exceptions, and the overwhelming majority of existing code does so.
Code that doesn't, though, tends to be that way for critically
important reasons which we would be foolish to second-guess. One
such reason is that it is meant to be called from a language that
cannot handle exceptions.

What would probably be actually tolerable would be to copy some similar
machinery from the existing C++ runtime, such as the unexpected().
(See ISO 14882, p345: section 18.6.2.2, [lib.unexpected.handler].)

Instead of the cancellation object's destructor registering terminate()
itself as the destructor, it would register another function which,
by default, calls terminate(). A program may call a function
"set_cancellation_destructor(void (*)());" and pass it a function
pointer to be called instead of terminate(), normally an empty
function. In that case discarding the cancellation would be like
discarding any other exception, and it is up to the author of the
code to ensure that the thread dies on schedule.

For stack-unwinding code, it suffices to register that runtime function,
perhaps "cancellation_destructor()", as the destructor for the exception
object. For naive users, it calls terminate(), minimizing debugging
surprises. Sophisticated users with special needs may instrument it
as needed, and will not be surprised at the results. Library writers
need not do anything special.

Nathan Myers
***@cantrip.org
William E. Kempf
2003-05-01 14:17:22 UTC
Permalink
Post by Nathan Myers
Post by Jason Merrill
Post by Richard Henderson
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as
the destructor for the exception object in the forced unwind case.
So if the catch(...) block rethrows, all is good, but if we exit the
catch block some other way, we try to clean up the exception, which
calls terminate.
Post by Richard Henderson
Oh, cool. This is a *much* nicer idea than I was contemplating.
Yes, I can agree to this. Uli, does this satisfy?
This means the object which is passed to the catch() code has a
destructor which is if necessary executed and can be prevented to be
called by explicitly rethrowing? This sounds excellent.
This alternative is almost tolerable. I will explain below.
First, though, we should run the choices by the ISO C++ committee and
the Boost.org list. The latter, particularly, have done a long and
deep analysis of C++ thread-library semantics, and have been
distributing for years a much-admired C++ binding to POSIX threads.
Large systems have been constructed using this library, leaving both a
wealth of experience and a huge mass of existing good code.
The Boost thread library implements delayed cancellation using a
distinguished exception, and a "cancelled" flag that is checked at
(many) cancellation points. (The Boost implementation is unable to
instrument many of the Posix cancellation points, such as read()
and write(), so its threads don't necessarily die as early as they
should.) User code routinely catches this exception and re-throws it.
Sometimes, though, the exception is caught and discarded, without
grave consequences.
Small clarification here. This is indeed the Boost.Threads design... but
it doesn't exist in the current implementation as released to the public.
It will be released with the next Boost release, hopefully.
Post by Nathan Myers
If a cancellation exception is caught and discarded, then at the next
cancellation point another is thrown. This has turned out to be
entirely practical. Since the "change of state" that Ulrich speaks of
amounts to a boolean flag being set, to be checked at cancellation
points, continuing out of a catch block has no more dire consequence
than delaying the death of the thread (which executing the destructors
does anyhow). Since destructors may execute arbitrary code, to say
that the thread state is somehow incapable of executing the code after
the catch block seems to call for more support than a bald assertion,
particularly given the Boost experience.
Because the Boost.org thread library is almost a shoo-in candidate for
adoption as part of the next Standard C++ Library, experience with it,
and its designers and implementers, must not be taken lightly. It
would be a grave error to implement runtime semantics fundamentally
incompatible with the Boost C++ threads library cancellation policy.
It's not necessarily a "soo-in", but there is at least interest. Just
remember, though, that there isn't a large amount of real-world experience
with this cancellation mechanism.
Post by Nathan Myers
Now, about the consequences of registering terminate() as the
destructor for the cancellation object... First, the essential
principle to observe in analyzing choices is that code compiled for use
by a thread has to work the same way in a non-threaded program, such as
a unit-test harness. Nobody can afford to maintain libraries that must
behave differently when called by a thread. Few can afford to compile
libraries differently when they might be linked with a threaded
program.
I only partially agree with this. You often can't program to this ideal,
if for no other reason than because thread synchronization requires
significant overhead. So many libraries do, in fact, get implemented to
compile differently when they might be linked with a threaded program. In
fact, many C RTLs supplied by vendors have threaded and non-threaded
variants.
Post by Nathan Myers
Many of the requirements observed by exception-safe code are
fortuitously very similar to those for thread-safe code, so it is
common practice to write libraries that are both exception-safe and
thread-safe. Since exception-handling code is usually the least
well-exercised part of any system, it is critical to keep it simple and
transparent. Accommodating two different behaviors eliminates
transparency.
What makes the proposal almost tolerable is that it is considered best
practice under almost all circumstances to rethrow unidentified
exceptions, and the overwhelming majority of existing code does so. Code
that doesn't, though, tends to be that way for critically
important reasons which we would be foolish to second-guess. One
such reason is that it is meant to be called from a language that
cannot handle exceptions.
Precisely.
Post by Nathan Myers
What would probably be actually tolerable would be to copy some similar
machinery from the existing C++ runtime, such as the unexpected(). (See
ISO 14882, p345: section 18.6.2.2, [lib.unexpected.handler].)
Instead of the cancellation object's destructor registering terminate()
itself as the destructor, it would register another function which, by
default, calls terminate(). A program may call a function
"set_cancellation_destructor(void (*)());" and pass it a function
pointer to be called instead of terminate(), normally an empty
function. In that case discarding the cancellation would be like
discarding any other exception, and it is up to the author of the code
to ensure that the thread dies on schedule.
On what schedule? Cooperative cancellation is a request, not a demand.
Ignoring the mechanism in which cancellation is carried out, with the
POSIX cooperative cancellation mechanism it is possible for a thread to be
"cancelled" but still never terminate. For instance, if it never calls a
cancellation point, or turns off cancellation detection before it does,
the thread can continue on indefinately, even though a request to be
cancelled has occurred. So, the only "schedule" is the explicit schedule
set by the thread being cancelled (not the thread making the request).
Terminating (and I assume when you say terminate() you aren't talking
about a thread_terminate() that would abort only the thread) in this case
seems like a sledge hammer being used to enforce a very strict exception
handling mechanism. I wouldn't have problems with an individual program
electing to do this, but the library should not do this (at least by
default, even if I can turn it off). Forcing a re-throw would be a better
idea... but even that was rejected during the discussion on the Boost
list, for the reasons you gave above. IOW, it's sometimes valid/necessary
to catch with out re-throwing, and isn't a violation of the cooperative
cancellation mechanism, so long as the cancelled state remains true.
--
William E. Kempf
David Abrahams
2003-05-01 15:23:41 UTC
Permalink
Post by William E. Kempf
Post by Nathan Myers
What makes the proposal almost tolerable is that it is considered best
practice under almost all circumstances to rethrow unidentified
exceptions, and the overwhelming majority of existing code does so.
And also that it doesn't cause a special case to be enshrined in the
core language definition (even if it's not the *standard* core
language, GCC will be stuck with the choices it makes for some time to
come).
Post by William E. Kempf
Post by Nathan Myers
Code
that doesn't, though, tends to be that way for critically
important reasons which we would be foolish to second-guess. One
such reason is that it is meant to be called from a language that
cannot handle exceptions.
Precisely.
I third that.

In particular, I work on a language binding library which really needs
to translate C++ exceptions into Python exceptions (which are not
exceptions at the C/C++ language level) at the language boundary. To
get the "right" behavior, which would continue unwinding through
Python code, I *must* eat the C++ exception. If my code doesn't know
anything specific about thread cancellation exceptions, it will try to
turn the exception into an "Unidentified" error, which will propagate
back through Python, almost certainly resulting in correct behavior.
Post by William E. Kempf
Post by Nathan Myers
What would probably be actually tolerable would be to copy some similar
machinery from the existing C++ runtime, such as the unexpected(). (See
ISO 14882, p345: section 18.6.2.2, [lib.unexpected.handler].)
Instead of the cancellation object's destructor registering terminate()
itself as the destructor, it would register another function which, by
default, calls terminate(). A program may call a function
"set_cancellation_destructor(void (*)());" and pass it a function
pointer to be called instead of terminate(), normally an empty
function. In that case discarding the cancellation would be like
discarding any other exception, and it is up to the author of the code
to ensure that the thread dies on schedule.
On what schedule? Cooperative cancellation is a request, not a demand.
That is a key point. It seems like we're having this discussion
because even though that is a basic pthreads design decision, it is
hard for the brain to accept.

There are very few correct programs which can "eat all exceptions and
plow ahead anyway". Is it really a design requirement that these
programs be transplantable *without modification* into an environment
where they run on a thread *and* where a thread cancellation _request_
can be assured of terminating them?

[and I note as you mention below: there is no assurance anyway]
Post by William E. Kempf
Ignoring the mechanism in which cancellation is carried out, with
the POSIX cooperative cancellation mechanism it is possible for a
thread to be "cancelled" but still never terminate. For instance,
if it never calls a cancellation point, or turns off cancellation
detection before it does, the thread can continue on indefinately,
even though a request to be cancelled has occurred. So, the only
"schedule" is the explicit schedule set by the thread being
cancelled (not the thread making the request). Terminating (and I
assume when you say terminate() you aren't talking about a
thread_terminate() that would abort only the thread) in this case
seems like a sledge hammer being used to enforce a very strict
exception handling mechanism.
Or rather, to bypass the EH mechanism and make "sure" (unreliably)
that the thread dies.
Post by William E. Kempf
I wouldn't have problems with an individual program electing to do
this, but the library should not do this (at least by default, even
if I can turn it off). Forcing a re-throw would be a better
idea... but even that was rejected during the discussion on the
Boost list, for the reasons you gave above. IOW, it's sometimes
valid/necessary to catch with out re-throwing
As in my Python binding case.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Fergus Henderson
2003-05-02 15:13:06 UTC
Permalink
Post by David Abrahams
There are very few correct programs which can "eat all exceptions and
plow ahead anyway". Is it really a design requirement that these
programs be transplantable *without modification* into an environment
where they run on a thread *and* where a thread cancellation _request_
can be assured of terminating them?
Unfortunately there are very few correct programs, period.

Likewise there are also very few robust programs. As systems get more
complex, robustness is becoming more and more important. It is in
general not feasible to build complex systems in which no component will
ever fail. So the only way to ensure robustness is to build systems
that can tolerate failures.

In C, it was easy to build systems which *ignored* failures.
In C++, it is reasonably easy to build systems which *report* failures,
by throwing exceptions when failures occur. However, in order to
build truly robust systems, it is not enough to just report failures.
The system needs to have some way of tolerating them, by catching
the exceptions and continuing.

Often the places where you can easily recover from exceptions
and continue will be far removed from the places that actually
throw the exceptions. Furthermore, because of abstraction,
the place which is attempting to recover and continue will
often not know what kinds of errors may occur, since those
will depend on the implementation details of opaque abstractions.

So, when writing robust programs, it is often necessary to catch *all*
exceptions. Idiom which rely on this, like the "try method A, and if
that doesn't work, try method B" idiom, are crucial to writing truly
robust large-scale complex systems, because they are basically the only
way to avoid the chance of overall system failure scaling linearly with
the number of steps that must be performed.

So, although I agree that there are currently very few _correct_ programs
which contain routines that catch all exceptions, I think that it is
also true to say that there very few _robust_ programs which do NOT contain
such routines.

In short, the idiom which you are trying to deprecate is crucial for
robustness, so it should not be so lightly abandoned!
--
Fergus Henderson <***@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
David Abrahams
2003-05-02 21:57:03 UTC
Permalink
Post by Fergus Henderson
Post by David Abrahams
There are very few correct programs which can "eat all exceptions and
plow ahead anyway". Is it really a design requirement that these
programs be transplantable *without modification* into an environment
where they run on a thread *and* where a thread cancellation _request_
can be assured of terminating them?
<snip>
Post by Fergus Henderson
So, although I agree that there are currently very few _correct_ programs
which contain routines that catch all exceptions, I think that it is
also true to say that there very few _robust_ programs which do NOT contain
such routines.
In short, the idiom which you are trying to deprecate is crucial for
robustness, so it should not be so lightly abandoned!
I'm hardly trying to deprecate it. I'm trying to suggest that the
places where eating everything and ploughing ahead is the right thing
to do are sufficiently rare that it might be unreasonable to expect
that these programs can suddenly become multithreaded without
modification, and that even if it weren't unreasonable, the
requirement that they be assuredly-cancellable might be unreasonable
(not to mention, of course, that it's in fact impossible to ensure any
particular response to a cancellation request).
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Nathan Myers
2003-05-01 16:52:46 UTC
Permalink
Post by William E. Kempf
Post by Nathan Myers
Post by Jason Merrill
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as
the destructor for the exception object in the forced unwind case.
So if the catch(...) block rethrows, all is good, but if we exit the
catch block some other way, we try to clean up the exception, which
calls terminate.
This alternative is almost tolerable. I will explain below.
[...]
the essential
principle to observe in analyzing choices is that code compiled for
use by a thread has to work the same way in a non-threaded program,
such as a unit-test harness. Nobody can afford to maintain
libraries that must behave differently when called by a thread.
Few can afford to compile libraries differently when they might be
linked with a threaded program.
I only partially agree with this. You often can't program to this
ideal, if for no other reason than because thread synchronization
requires significant overhead. So many libraries do, in fact, get
implemented to compile differently when they might be linked with a
threaded program. In fact, many C RTLs supplied by vendors have
threaded and non-threaded variants.
The paragraph quoted was an argument that library maintainers cannot
be expected to manage libraries in which catch(...) clauses are skipped
during unwinding.

While it is still common for C libraries to have threaded and non-
threaded variants, those variants tend to differ only in whether locking
and unlocking operations are no-ops, rather than in the semantics of
their basic control-flow constructs. (Even so they are a serious
nuisance.) Imagine if you could not even test the code without running
both a threaded and an unthreaded program, because the control flow
paths would be different.
Post by William E. Kempf
Post by Nathan Myers
What would probably be actually tolerable would be to copy some similar
machinery from the existing C++ runtime, such as the unexpected(). (See
ISO 14882, p345: section 18.6.2.2, [lib.unexpected.handler].)
Instead of the cancellation object's destructor registering terminate()
itself as the destructor, it would register another function which, by
default, calls terminate(). A program may call a function
"set_cancellation_destructor(void (*)());" and pass it a function
pointer to be called instead of terminate(), normally an empty
function. In that case discarding the cancellation would be like
discarding any other exception, and it is up to the author of the code
to ensure that the thread dies on schedule.
On what schedule? Cooperative cancellation is a request, not a
demand. Ignoring the mechanism in which cancellation is carried out,
with the POSIX cooperative cancellation mechanism it is possible for a
thread to be "cancelled" but still never terminate. For instance, if
it never calls a cancellation point, or turns off cancellation
detection before it does, the thread can continue on indefinately,
even though a request to be cancelled has occurred. So, the only
"schedule" is the explicit schedule set by the thread being cancelled
(not the thread making the request).
There are three parties involved:

- the thread being cancelled,
- the complete program, the source of the cancellation,
- the library which has state in the thread's execution context.

"Schedule" refers to the complete program's expectation that the
thread will die, and soon enough, whatever that turns out to mean.
If it doesn't, the program has failed. The library author cannot be
presumed to know anything about the goals of the complete program's
author. A library can only be coded according to reasonable
best-practice rules which the program author must verify are compatible
with its use in the complete program.

We have to make sure the rules are clear enough and simple enough
that it's practical to meet the needs of both library authors and
program authors.
Post by William E. Kempf
Terminating (and I assume when you say terminate() you aren't talking
about a thread_terminate() that would abort only the thread) in this
case seems like a sledge hammer being used to enforce a very strict
exception handling mechanism. I wouldn't have problems with an
individual program electing to do this, but the library should not do
this (at least by default, even if I can turn it off). Forcing a
re-throw would be a better idea... but even that was rejected during
the discussion on the Boost list, for the reasons you gave above. IOW,
it's sometimes valid/necessary to catch without re-throwing, and isn't
a violation of the cooperative cancellation mechanism, so long as the
cancelled state remains true.
The use of termination (i.e. the call to std::terminate()) here is
analogous to its use when exception handling has got too bollixed up
to continue, such as when it is called by unexpected() (See 15.5.1).

The purpose is to prevent spurious bug reports from users who have
corrupted their process state all unawares. Calling terminate() means
the error is detected immediately and unambiguously. As a default,
therefore, it's reasonably safe. A sophisticated user must be equipped
to turn it off, though, and deal with the consequences,

I agree that it would be better to make the cancellation event behave
as an ordinary exception, but the thread library maintainers seem
unwilling to implement it. Programs that want that behavior would
have to add (perhaps) "set_cancellation_destructor(0)" to the common
preamble at the beginning of main(), along with the
"std::ios::sync_with_stdio(false);" and "signal(SIGPIPE, SIG_IGN);"
we are already accustomed to doing. :-P

A compiler flag, perhaps "-Wrethrow", might enable warnings about
falling off the end of a "catch(...)" block.

Nathan Myers
***@cantrip.org
William E. Kempf
2003-05-01 17:26:36 UTC
Permalink
Post by William E. Kempf
I only partially agree with this. You often can't program to this
ideal, if for no other reason than because thread synchronization
requires significant overhead. So many libraries do, in fact, get
implemented to compile differently when they might be linked with a
threaded program. In fact, many C RTLs supplied by vendors have
threaded and non-threaded variants.
The paragraph quoted was an argument that library maintainers cannot be
expected to manage libraries in which catch(...) clauses are skipped
during unwinding.
While it is still common for C libraries to have threaded and non-
threaded variants, those variants tend to differ only in whether locking
and unlocking operations are no-ops, rather than in the semantics of
their basic control-flow constructs. (Even so they are a serious
nuisance.) Imagine if you could not even test the code without running
both a threaded and an unthreaded program, because the control flow
paths would be different.
Total agreement.
Post by William E. Kempf
Post by Nathan Myers
What would probably be actually tolerable would be to copy some
similar machinery from the existing C++ runtime, such as the
unexpected(). (See ISO 14882, p345: section 18.6.2.2,
[lib.unexpected.handler].)
Post by Nathan Myers
Instead of the cancellation object's destructor registering
terminate() itself as the destructor, it would register another
function which, by default, calls terminate(). A program may call
a function
Post by Nathan Myers
"set_cancellation_destructor(void (*)());" and pass it a function
pointer to be called instead of terminate(), normally an empty
function. In that case discarding the cancellation would be like
discarding any other exception, and it is up to the author of the
code to ensure that the thread dies on schedule.
On what schedule? Cooperative cancellation is a request, not a
demand. Ignoring the mechanism in which cancellation is carried out,
with the POSIX cooperative cancellation mechanism it is possible for a
thread to be "cancelled" but still never terminate. For instance, if
it never calls a cancellation point, or turns off cancellation
detection before it does, the thread can continue on indefinately,
even though a request to be cancelled has occurred. So, the only
"schedule" is the explicit schedule set by the thread being cancelled
(not the thread making the request).
- the thread being cancelled,
- the complete program, the source of the cancellation,
- the library which has state in the thread's execution context.
"Schedule" refers to the complete program's expectation that the
thread will die, and soon enough, whatever that turns out to mean. If
it doesn't, the program has failed. The library author cannot be
presumed to know anything about the goals of the complete program's
author. A library can only be coded according to reasonable
best-practice rules which the program author must verify are compatible
with its use in the complete program.
Totally agreement. My point was, the only party that has control over the
schedule is the thread being cancelled. This is true regardless of the
cancellation implementation, assuming a cooperative form of cancellation
(and asynchronous cancellation is a totally different topic with very
different consequences and requirements). Given this fact, you're doing
no one any favors by preventing the thread from "eating the exception".
This prevention only restricts how the thread can fullfill the conceptual
contract, while not giving the other parties any more gaurantees that the
"schedule" will be adhered to. The "schedule" is conceptual, and requires
strict and _explicit_ cooperation among the parties involved.
We have to make sure the rules are clear enough and simple enough
that it's practical to meet the needs of both library authors and
program authors.
This is best done by imposing no new requirements on the "cancellation
exception". This does not make it any more difficult for the thread being
cancelled to fullfill the request in a timely manner, and more
importantly, when compared with altering the exception requirements,
imposes no restrictions that can actually make it more difficult to
fullfill the request in a timely and *correct* manner. (The emphasis on
correct is due to the cases in which it's necessary, or at least less
problematic, to eat the exception because of usage requirements, such as
interaction with languages that can't deal with exceptions.)
Post by William E. Kempf
Terminating (and I assume when you say terminate() you aren't talking
about a thread_terminate() that would abort only the thread) in this
case seems like a sledge hammer being used to enforce a very strict
exception handling mechanism. I wouldn't have problems with an
individual program electing to do this, but the library should not do
this (at least by default, even if I can turn it off). Forcing a
re-throw would be a better idea... but even that was rejected during
the discussion on the Boost list, for the reasons you gave above. IOW,
it's sometimes valid/necessary to catch without re-throwing, and isn't
a violation of the cooperative cancellation mechanism, so long as the
cancelled state remains true.
The use of termination (i.e. the call to std::terminate()) here is
analogous to its use when exception handling has got too bollixed up to
continue, such as when it is called by unexpected() (See 15.5.1).
The purpose is to prevent spurious bug reports from users who have
corrupted their process state all unawares. Calling terminate() means
the error is detected immediately and unambiguously. As a default,
therefore, it's reasonably safe. A sophisticated user must be equipped
to turn it off, though, and deal with the consequences,
Maybe I'm misunderstanding, but I thought the call to terminate under
discussion here occurs when the exception is caught but not re-thrown? If
that's the case, what process state would be corrupted in this case? All
invariants should still hold.
I agree that it would be better to make the cancellation event behave as
an ordinary exception, but the thread library maintainers seem
unwilling to implement it. Programs that want that behavior would have
to add (perhaps) "set_cancellation_destructor(0)" to the common
preamble at the beginning of main(), along with the
"std::ios::sync_with_stdio(false);" and "signal(SIGPIPE, SIG_IGN);" we
are already accustomed to doing. :-P
Why are the "thread library maintainers... unwilling to implement it"?
What concerns do they have with the implementation? Why is terminate()
considered better? It seems to me that it only makes things more complex,
not safer.
A compiler flag, perhaps "-Wrethrow", might enable warnings about
falling off the end of a "catch(...)" block.
A diagnostic sounds reasonable. More so than a runtime mechanism that may
not be caught in testing. And such a diagnostic might make sense for all
exception types, not just cancellation.
--
William E. Kempf
Nathan Myers
2003-05-01 19:09:40 UTC
Permalink
I should introduce William Kempf to the rest of the readers.
He is the primary author of the Boost C++ threads library.
Post by William E. Kempf
Post by William E. Kempf
On what schedule? Cooperative cancellation is a request, not a
demand. ...
... A library can only be coded according to reasonable
best-practice rules which the program author must verify are
compatible with its use in the complete program.
Totally agreement. My point was, the only party that has control over
the schedule is the thread being cancelled.
Exactly. The author of the complete program can afford to use the
library only if the library author makes a promise that it will not
interfere with thread cancellation. Practically, this can be true
only if "not interfering" is identical to normal best practice,
because library authors cannot afford to take into account all kinds
of special domain requirements and maintain the power-set of versions
for all likely combinations of requirements.
Post by William E. Kempf
[...] The "schedule" is conceptual, and requires strict and _explicit_
cooperation among the parties involved.
...unless this cooperation is the norm, in which case the library
author must explicitly notify users that a particular library is not
coded normally. "Doesn't re-throw unidentified exceptions" is a
property that any library user would have to know about anyhow --
threads or no threads.
Post by William E. Kempf
We have to make sure the rules are clear enough and simple enough
that it's practical to meet the needs of both library authors and
program authors.
This is best done by imposing no new requirements on the "cancellation
exception". This does not make it any more difficult for the thread
being cancelled to fullfill the request in a timely manner, and more
importantly, when compared with altering the exception requirements,
imposes no restrictions that can actually make it more difficult to
fullfill the request in a timely and *correct* manner. (The emphasis
on correct is due to the cases in which it's necessary, or at least
less problematic, to eat the exception because of usage requirements,
such as interaction with languages that can't deal with exceptions.)
Agreed, but see below.
Post by William E. Kempf
Post by William E. Kempf
Terminating [...] in this case seems like a sledge hammer ...
The purpose is to prevent spurious bug reports from users who have
corrupted their process state all unawares. ...
Maybe I'm misunderstanding, but I thought the call to terminate under
discussion here occurs when the exception is caught but not re-thrown?
If that's the case, what process state would be corrupted in this
case? All invariants should still hold.
Agreed, but see below.
Post by William E. Kempf
I agree that it would be better to make the cancellation event
behave as an ordinary exception, but the thread library maintainers
seem unwilling to implement it. Programs that want that behavior
would have to add (perhaps) "set_cancellation_destructor(0)" to the
common preamble at the beginning of main(), along with the
"std::ios::sync_with_stdio(false);" and "signal(SIGPIPE, SIG_IGN);"
we are already accustomed to doing. :-P
Why are the "thread library maintainers... unwilling to implement it"?
What concerns do they have with the implementation? Why is
terminate() considered better? It seems to me that it only makes
things more complex, not safer.
You should understand that we are corresponding with implementers
of GNU Libc and Gcc who are building a native Posix thread library
on Linux, integrating details of library, code generation and the
kernel for best performance.

As I understand it, in this implementation, a variety of operations
available to a thread before it has been cancelled result in
undefined behavior if called after the cancellation (including e.g.
thread_exit() and longjmp()). The implementers fear that allowing
a cancelled thread to run off the end of the catch clause makes it
too easy to run into code that evokes some undefined behavior.

Making it too easy for users to evoke undefined behavior leads to
spurious but insistent bug reports from customers who may be hard to
put off. If a user must positively assert that the danger is known
and accepted, it gets much more unlikely that she will complain
about the consequences.

So, we're talking about a compromise. We know what we want. They
know what they're willing to implement. We just need to agree
on something that satisfies everyone. In that light, the question
is: is set_cancellation_destructor() the best possible compromise,
or is something more clever, that would make you happier, possible?

The code written thus far runs destructors but skips catch clauses.
The compromise Jason proposed calls terminate() if a cancellation
is discarded. The compromise I proposed does that by default, but
can be turned off. Can you do better?
Post by William E. Kempf
A compiler flag, perhaps "-Wrethrow", might enable warnings about
falling off the end of a "catch(...)" block.
A diagnostic sounds reasonable. More so than a runtime mechanism that
may not be caught in testing. And such a diagnostic might make sense
for all exception types, not just cancellation.
Just so. The goal is to avoid special semantics and special ways of
coding for libraries that might be used in threaded programs, so
that writing exception-safe and thread-safe code are just the same
as generically good coding. That's generally impossible in C, but
normal in C++.

Nathan Myers
***@cantrip.org
William E. Kempf
2003-05-01 21:38:55 UTC
Permalink
Post by William E. Kempf
Why are the "thread library maintainers... unwilling to implement it"?
What concerns do they have with the implementation? Why is
terminate() considered better? It seems to me that it only makes
things more complex, not safer.
You should understand that we are corresponding with implementers of
GNU Libc and Gcc who are building a native Posix thread library on
Linux, integrating details of library, code generation and the kernel
for best performance.
As I understand it, in this implementation, a variety of operations
available to a thread before it has been cancelled result in
undefined behavior if called after the cancellation (including e.g.
thread_exit() and longjmp()). The implementers fear that allowing a
cancelled thread to run off the end of the catch clause makes it too
easy to run into code that evokes some undefined behavior.
OK, this helps me understand... but I need a bit more. Which standard,
and what clauses, declare this to be undefined behavior, and why? And
since no standard that I'm aware of speaks towards threading and C++,
aren't you already providing definitions for undefined territory? Can't
you do the same in this case? If cancellation is implemented in terms of
exception throwing, why would calls to thread_exit() and longjump() be
problematic to this implementation? Even if it is impossible to call
these, wouldn't that be something that's the responsibility of the
programmer to ensure isn't done? Why should simply not re-throwing the
exception cause termination? Wouldn't it make more sense for the other
calls to call terminate() if the thread were cancelled (which would
require two "flags", one to indicate the cancellation request and one to
indicate the initiation after a cancellation point)?

Sorry, I've come in on the middle of this discussion, and it appears
there's some things I'm not up to speed on.
Making it too easy for users to evoke undefined behavior leads to
spurious but insistent bug reports from customers who may be hard to
put off. If a user must positively assert that the danger is known and
accepted, it gets much more unlikely that she will complain
about the consequences.
So, we're talking about a compromise. We know what we want. They know
what they're willing to implement. We just need to agree
is set_cancellation_destructor() the best possible compromise, or is
something more clever, that would make you happier, possible?
I don't know yet... I need some answers to the above questions.
The code written thus far runs destructors but skips catch clauses. The
compromise Jason proposed calls terminate() if a cancellation
is discarded. The compromise I proposed does that by default, but can
be turned off. Can you do better?
I don't know. I'm still trying to grasp why terminate() is considered a
good (or even acceptable) choice here.

Just to make sure everyone is aware of it, Mr.Butenhof, a noted POSIX
threading expert, has done work on Tru64 along these very same lines
(http://sources.redhat.com/ml/libc-alpha/1999-08/msg00038.html). I'm
trying to do the research at this moment to give a definative answer, but
I've never seen an indication that Tru64 has done anything along the lines
of calling terminate() if the cancellation exception were not re-thrown.
Post by William E. Kempf
Post by Nathan Myers
A compiler flag, perhaps "-Wrethrow", might enable warnings about
falling off the end of a "catch(...)" block.
A diagnostic sounds reasonable. More so than a runtime mechanism that
may not be caught in testing. And such a diagnostic might make sense
for all exception types, not just cancellation.
Just so. The goal is to avoid special semantics and special ways of
coding for libraries that might be used in threaded programs, so
that writing exception-safe and thread-safe code are just the same as
generically good coding. That's generally impossible in C, but normal
in C++.
Given that goal, I would think that calling terminate() would NOT be a
valid choice. This makes the cancellation exception unique, and means
writing exception-safe code and thread-safe code are not the same as
writing "generically good code".

try
{
foo();
}
catch (...)
{
// do nothing
}

Perfectly valid and reasonable for exception-safe code, at least in some
use cases. However, if the cancellation exception can cause a call to
terminate(), the above is no longer reasonable, and as a programmer I'd
have to know whether or not foo() can follow any execution path that leads
to a cancellation point to know if I have to take the precaution to use a
mechanism to turn off the call to terminate(). And what about code that's
already been written like this, and is out of my control to change,
because there was no concern about terminate() being called?
--
William E. Kempf
Mark Mitchell
2003-05-01 21:51:23 UTC
Permalink
Post by William E. Kempf
I don't know. I'm still trying to grasp why terminate() is considered a
good (or even acceptable) choice here.
I suggest you go read this entire thread:

http://gcc.gnu.org/ml/gcc-patches/2003-04/msg02246.html

so that you have more context.

It's not considered good by any of the C++ people, as far as I can
tell. The only acceptable thing about calling terminate when a
catch(...) does not rethrow is that it is (a) better than skipping the
catch(...) entirely, which was the original suggestion, and (b) provides
a forward-compatible way to fix things later (in that you can just not
call terminate and let the exception propagate as it should.)
Post by William E. Kempf
Just to make sure everyone is aware of it, Mr.Butenhof, a noted POSIX
threading expert, has done work on Tru64 along these very same lines
(http://sources.redhat.com/ml/libc-alpha/1999-08/msg00038.html).
Yes, this is precisely what I, Nathan Myers, and others, have argued is
the right answer.

At this point, we're merrily going around telling each other how much we
all agree with each other, but none of us is the person who can actually
influence the outcome. That's up to Ulrich Drepper, copied above, and
he is apparently as-of-yet unpersuaded.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
William E. Kempf
2003-05-01 22:36:37 UTC
Permalink
Post by Mark Mitchell
Post by William E. Kempf
I don't know. I'm still trying to grasp why terminate() is considered
a good (or even acceptable) choice here.
http://gcc.gnu.org/ml/gcc-patches/2003-04/msg02246.html
so that you have more context.
Thanks. I have one posting in particular that sumarizes some of what I've
been saying nicely, and allows me to make a few more observations:
http://gcc.gnu.org/ml/gcc-patches/2003-04/msg02279.html.

"I think the key design goal should be to make correct single-threaded
code usable in a multi-threaded program, with as little modification as
possible."

As I tried to point out in my last reply, this goal isn't met if
terminate() is called. In a single threaded compile, this is safe, as
pointed out in the thread:

f = fopen(...);
try {
foo();
} catch (...) {
}
fclose (f);

When compiled as a multi-threaded compile, it's safe only if the catch()
block is run, and terminate() is not called when a cancellation exception
is not re-thrown. The reason is that I don't know if foo() is an implicit
cancellation point (i.e. it calls an explicit cancellation point in some
code path).

"If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled."

Or even *if* it will be cancelled.

I have to leave for the night, and probably won't be able to read further
or comment more until tomorrow. But I do have one final thought right
now. One of the reasons given for using terminate() is that it makes it
easier for support to pinpoint the programmer error if certain routines
(long_jump() and thread_exit() were given as examples) were called after
cancellation, which is supposed to result in undefined behavior. Well,
what prevents these from being called in destructors or other cleanup
handlers? Why should the catch() be treated any differently than these
cases?
Post by Mark Mitchell
Post by William E. Kempf
Just to make sure everyone is aware of it, Mr.Butenhof, a noted POSIX
threading expert, has done work on Tru64 along these very same lines
(http://sources.redhat.com/ml/libc-alpha/1999-08/msg00038.html).
Yes, this is precisely what I, Nathan Myers, and others, have argued is
the right answer.
At this point, we're merrily going around telling each other how much we
all agree with each other, but none of us is the person who can actually
influence the outcome. That's up to Ulrich Drepper, copied above, and
he is apparently as-of-yet unpersuaded.
Well, if it helps, Tru64 is likely to be the "existing practice" that
helps to decide what I'll suggest to the C++ committee, and as near as I
can tell in my readings so far, it does not prevent the user from catching
and not re-throwing the cancellation (or exit!) exception. So I'm likely
to suggest the same, unless it can be shown that this is technically a bad
idea. So, I know you want to convince Ulrich Drepper that it should be as
I've described, but in trying to convince anybody, I'm turning it around
asking that you convince me it shouldn't be, or more precisely, that
calling terminate() is even viable, let alone preferred. However, if that
is my suggestion, and the committee agrees/accepts the proposal, you'll
have to change the behavior to not terminate() at that point, anyhow. So,
I'm providing at least some arguments to help convince people even by
this.

I'll go do my research here and continue this tomorrow.
--
William E. Kempf
Mark Mitchell
2003-05-01 23:25:32 UTC
Permalink
Post by William E. Kempf
Well, if it helps, Tru64 is likely to be the "existing practice" that
helps to decide what I'll suggest to the C++ committee, and as near as I
can tell in my readings so far, it does not prevent the user from catching
and not re-throwing the cancellation (or exit!) exception. So I'm likely
to suggest the same, unless it can be shown that this is technically a bad
idea. So, I know you want to convince Ulrich Drepper that it should be as
I've described, but in trying to convince anybody, I'm turning it around
asking that you convince me it shouldn't be, or more precisely, that
calling terminate() is even viable, let alone preferred. However, if that
is my suggestion, and the committee agrees/accepts the proposal, you'll
have to change the behavior to not terminate() at that point, anyhow. So,
I'm providing at least some arguments to help convince people even by
this.
I think you're misunderstanding the situation.

I'm not arguing that std::terminate is the right thing to do.

I'm not arguing that's even a good thing.

I'm not going to try to convince you it's a good thing.

You and I are on the same page. :-)

The only reason std::terminate came into this discussion at all was that
it appeared impossible to get Ulrich to implement The Right Thing. In
particular there were two classes of programs, and everyone agreed on
the behavior for one class. For the other class, we did not all agree.
I suggested we call terminate for programs in that second class because
it is better to call terminate that to silently do The Wrong Thing,
which was the only other proposal on the table.

See you in Kona,
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Gabriel Dos Reis
2003-05-02 13:35:27 UTC
Permalink
Mark Mitchell <***@codesourcery.com> writes:

[...]

| I suggested we call terminate for programs in that second class because
| it is better to call terminate that to silently do The Wrong Thing,
| which was the only other proposal on the table.

That was the way I understood your suggestion.

-- Gaby
William E. Kempf
2003-05-02 15:16:48 UTC
Permalink
Post by Mark Mitchell
The only reason std::terminate came into this discussion at all was that
it appeared impossible to get Ulrich to implement The Right Thing. In
particular there were two classes of programs, and everyone agreed on
the behavior for one class. For the other class, we did not all agree.
I suggested we call terminate for programs in that second class because
it is better to call terminate that to silently do The Wrong Thing,
which was the only other proposal on the table.
OK... I've been doing further reading/research on the thread topic. Let's
take individual postings I find of interest one at a time:
http://gcc.gnu.org/ml/gcc-patches/2003-04/msg02284.html
Post by Mark Mitchell
Post by Mark Mitchell
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler. Deferred cancellation
semantics don't give you any guarantees about when a thread will exit
after it has been cancelled.
No, this is not acceptable. Once cancelled a thread cannot be allowed
to prevent this. Cancellation changes the internal state of the thread,
it cannot continue running. The POSIX standard is clear on that, if a
thread leaves the cleanup handler in some way the behavior is undefined.
The rethrow is mandatory in case the catch blocks are expected to run.
There is no discussion possible, this is a fact.
First, I have to say that I don't have a copy of the actual standard. I'm
using the publications of the Open Group as reference:
http://www.opengroup.org/. I can't find any reference indicating that a
thread cannot continue running once cancelled. In fact, it appears to be
exactly the opposite, since a cleanup handler is allowed to call nearly
anything (see the next point).

http://www.opengroup.org/onlinepubs/007904975/ indicates that the only
thing you can't do within a cleanup handler without invoking undefined
behavior is to call setjump/longjump. Calls to pthread_exit, or anything
else for that matter, should be well defined. It further says:

"This volume of IEEE Std 1003.1-2001 currently leaves unspecified the
effect of calling longjmp() from a signal handler executing in a POSIX
System Interfaces function. If an implementation wants to allow this and
give the programmer reasonable behavior, the longjmp() function has to
call all cancellation cleanup handlers that have been pushed but not
popped since the time setjmp() was called."

So an implementation CAN allow this, though it would still be undefined
behavior. In C++, assuming cancellation is implemented via the exception
mechanism, things should be well defined. It goes on to say:

"Note that the specified cleanup handling mechanism is especially tied to
the C language and, while the requirement for a uniform mechanism for
expressing cleanup is language-independent, the mechanism used in other
languages may be quite different. In addition, this mechanism is really
only necessary due to the lack of a real exception mechanism in the C
language, which would be the ideal solution."

This indicates that in other language bindings where exceptions are
available, it's expected that thread cancellation be implemented in terms
of those exceptions. This means obeying the rules of exceptions for those
languages, as doing anything else will leave those languages in a tenuous
state. As a C++ programmer, if I can't rely on cancellation "playing
nice", which means behaving exactly like an actual exception, then I can't
rely on cancellation at all, and must disable it. That's the situation
today, since there's no standard C++ binding for POSIX or any other
threading library. Ignoring the standards for a moment, if you're trying
to implement POSIX threads in a manner that's safe for C++, even if it's a
non-standard platform extension (which is a noble goal in and of itself),
you should go the full way in doing this. The one example I know of where
this has been done is on Tru64 (which is not to say other's haven't done
it as well)... and thread cancellation is fully implemented in terms of
exceptions there, even in C. This means you can catch the exception (even
in C) and can stop it's propagation.
Post by Mark Mitchell
From another thread: http://gcc.gnu.org/ml/gcc-patches/2003-04/msg02285.html
Post by Mark Mitchell
If the catch-clause chooses not to rethrow the exception, that just
means this thread isn't going to be cancelled, which could happen from
an ordinary pthread cancellation handler.
It could, but that would be a bug in the program. A cancellation request,
when acted on, is defined to be equivalent to pthread_exit, and there is
nothing to suggest that pthread_exit sometimes doesn't actually terminate
the thread.
Is cancellation defined to be equivalent? The Open Group publication
defines them with the same verbiage with regard to cleanup, but they
aren't totally equivalent and certainly not defined in terms of one
another. In any event, I also don't see anything specifically preventing
pthread_exit from not actually terminating the thread, and on Tru64 this
should even be possible! Of course, it would be a violation of the
implied contract, and as such would be a programmer error... but the
situation is a little different for cancellation. The purpose of
pthread_exit is to terminate with a result, while cancellation is just a
mechanism by which a secondary thread can request that the current thread
end gracefully. Ending gracefully in C++ means running destructors and
catch blocks *in the same manner in which they are run for any other
exception*.

Just to add another monkey wrench to the whole discussion, ponder this
snippet of code:

try
{
foo(); // calls a cancellation point
}
catch (...)
{
throw some_other_exception();
}

Some interesting links on this whole topic:

http://groups.google.com/groups?q=cancellation+exception+rethrow&hl=en&lr=&ie=UTF-8&selm=c29b5e33.0202140654.73ce8ced%40posting.google.com&rnum=4

http://groups.google.com/groups?q=cancellation+exception+rethrow&hl=en&lr=&ie=UTF-8&selm=3D230879.F56DA7E1%40web.de&rnum=5

http://groups.google.com/groups?q=cancellation+exception+rethrow&start=10&hl=en&lr=&ie=UTF-8&selm=c29b5e33.0201230342.579881e5%40posting.google.com&rnum=19

hoping-i-at-least-added-some-food-for-thought-ly yours...

William E. Kempf
David Abrahams
2003-05-01 15:27:00 UTC
Permalink
Post by Nathan Myers
Many of the requirements observed by exception-safe code are
fortuitously very similar to those for thread-safe code, so it is
common practice to write libraries that are both exception-safe and
thread-safe. Since exception-handling code is usually the least
well-exercised part of any system, it is critical to keep it simple
and transparent. Accommodating two different behaviors eliminates
transparency.
That's a very good point.

The only special tool I would need for handling thread cancellation
exceptions, as a programmer writing exception- and thread-safe code,
is a C++ name for these exception types so that I can catch and handle
them explicitly.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Mark Mitchell
2003-05-01 03:39:38 UTC
Permalink
Post by Jason Merrill
Post by Mark Mitchell
If it is really true that we cannot enter "catch(...)" blocks for some
reason (which I do not yet believe, despite Ulrich's usual inimitable
expression of confidence), then running into a "catch(...)" block while
unwinding should result in a call to std::terminate.
This makes sense to me.
Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case. So if the
catch(...) block rethrows, all is good, but if we exit the catch block some
other way, we try to clean up the exception, which calls terminate.
This is a good extension to my suggestion; it allows strictly more
non-controversial cases to work as we all think they should while
allowing us to debate the controversial cases as time permits.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Alexandre Oliva
2003-05-01 04:16:53 UTC
Permalink
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case.
How about, instead of terminate(), a function that rethrows the
cancellation?
--
Alexandre Oliva Enjoy Guarana', see http://www.ic.unicamp.br/~oliva/
Red Hat GCC Developer aoliva@{redhat.com, gcc.gnu.org}
CS PhD student at IC-Unicamp oliva@{lsd.ic.unicamp.br, gnu.org}
Free Software Evangelist Professional serial bug killer
Nathan Myers
2003-05-01 04:56:19 UTC
Permalink
Post by Alexandre Oliva
Post by Jason Merrill
Actually, I have an idea for how to DTRT here: give terminate() as the
destructor for the exception object in the forced unwind case.
How about, instead of terminate(), a function that rethrows the
cancellation?
No, that's a default, invisible change to exception semantics.

User code that doesn't re-throw generally doesn't for serious reasons.
If it's the wrong reason, they need to find out, not just silently
have their process state silently corrupted.

Nathan Myers
***@cantrip.org
Mark Mitchell
2003-05-01 04:49:32 UTC
Permalink
Post by Mark Mitchell
(4) Some other black magic in except.c that I couldn't get my head
around on the first try. What do these bits do?
Now that we've made you rewrite your patch for the nth time, how close
are you to being done? :-)

Seriously, I'm sorry that you got jerked around here by me reopening the
debate after you thought things were settled.

I'm hoping that Jason's semantics will make things easier; you now don't
have to skip catch clauses, but can rather treat this like an ordinary
exception.

We're still blocked for 3.3 on the V3 memory leaks, so as long as you
can get this stuff done in the next few days, we can certainly get it
into 3.3.
--
Mark Mitchell <***@codesourcery.com>
CodeSourcery, LLC
Richard Henderson
2003-05-01 05:13:51 UTC
Permalink
Post by Mark Mitchell
Now that we've made you rewrite your patch for the nth time, how close
are you to being done? :-)
Fairly close, I think. Most of the patch will be *reverting*
bits of the previous patch -- e.g. the -fforced-unwind-exceptions
flag can go away.

We still need to notice which ISO C functions are POSIX
cancellation points, but I suggest we simply do this all the
time rather than under the control of some flag -- the benefit
of doing otherwise will surely be minimal.

We still need some of the changes to libsupc++ that also
apply to true foreign language exceptions.
Post by Mark Mitchell
We're still blocked for 3.3 on the V3 memory leaks, so as long as you
can get this stuff done in the next few days, we can certainly get it
into 3.3.
I hope to have it done tomorrow. (Well, I hoped to have it
done today, but that clearly hasn't worked out. ;-)


r~
Mark Mitchell
2003-05-01 05:23:58 UTC
Permalink
Post by Richard Henderson
Post by Mark Mitchell
Now that we've made you rewrite your patch for the nth time, how close
are you to being done? :-)
Fairly close, I think.
Good.
Post by Richard Henderson
We still need to notice which ISO C functions are POSIX
cancellation points, but I suggest we simply do this all the
time rather than under the control of some flag -- the benefit
of doing otherwise will surely be minimal.
Agreed. In fact, as Nathan Myers points out, if we can compile code so
that it works either single-thread or multi-threaded, that's a big win.
So, if we just always assume that POSIX cancellation points can throw, I
think that's fine.

Thanks,
Post by Richard Henderson
--
CodeSourcery, LLC
Continue reading on narkive:
Loading...