summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Wingo <wingo@pobox.com>2017-01-08 15:42:22 +0100
committerAndy Wingo <wingo@pobox.com>2017-01-08 15:42:22 +0100
commitb392d81c9ca0bec82b43ca49e8cf96c0e9460a89 (patch)
tree5038de864e53e927be7ebeb7ef03567723e4a20d
parentdffe495d0de1466f62a91a6d74cc0f388e0f4f3f (diff)
downloadguile-b392d81c9ca0bec82b43ca49e8cf96c0e9460a89.tar.gz
Fix close-port race.
* libguile/ports.c (release_port): Fix race.
-rw-r--r--libguile/ports.c23
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,