diff options
author | Andy Wingo <wingo@pobox.com> | 2017-01-08 15:42:22 +0100 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2017-01-08 15:42:22 +0100 |
commit | b392d81c9ca0bec82b43ca49e8cf96c0e9460a89 (patch) | |
tree | 5038de864e53e927be7ebeb7ef03567723e4a20d | |
parent | dffe495d0de1466f62a91a6d74cc0f388e0f4f3f (diff) | |
download | guile-b392d81c9ca0bec82b43ca49e8cf96c0e9460a89.tar.gz |
Fix close-port race.
* libguile/ports.c (release_port): Fix race.
-rw-r--r-- | libguile/ports.c | 23 |
1 files changed, 15 insertions, 8 deletions
diff --git a/libguile/ports.c b/libguile/ports.c index 20319bc0b..f415453c4 100644 --- a/libguile/ports.c +++ b/libguile/ports.c @@ -134,15 +134,22 @@ static void release_port (SCM port) { scm_t_port *pt = SCM_PORT (port); - scm_t_uint32 prev; - prev = scm_atomic_subtract_uint32 (&pt->refcount, 1); - if (prev == 0) - /* Logic failure. */ - abort (); - - if (prev > 1) - /* Port still alive. */ + /* It's possible for two close-port invocations to race, and since + close-port is defined to be idempotent we need to avoid + decrementing the refcount past 0. The normal case is that it's + open with a refcount of 1 and we're going to change it to 0. + Otherwise if the refcount is higher we just subtract 1 and we're + done. However if the current refcount is 0 then the port has been + closed or is closing and we just return. */ + scm_t_uint32 cur = 1, next = 0; + while (!scm_atomic_compare_and_swap_uint32 (&pt->refcount, &cur, next)) + { + if (cur == 0) + return; + next = cur - 1; + } + if (cur > 1) return; /* FIXME: `catch' around the close call? It could throw an exception, |