Skip to content
This repository has been archived by the owner on May 2, 2018. It is now read-only.

Commit

Permalink
spec, runtime, tests: send on closed channel panics
Browse files Browse the repository at this point in the history
Close of closed channel panics.
Receive from closed channel never panics,
even if done repeatedly.

Fixes #1349.
Fixes #1419.

R=gri, iant, ken2, r, gri1, r2, iant2, rog, albert.strasheim, niemeyer, ejsherry
CC=golang-dev
https://golang.org/cl/3989042
  • Loading branch information
rsc committed Jan 21, 2011
1 parent 0a5fc26 commit 27c74d3
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 40 deletions.
2 changes: 1 addition & 1 deletion doc/go_spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -4394,7 +4394,7 @@ <h3 id="Close_and_closed">Close and closed</h3>
<p>
For a channel <code>c</code>, the built-in function <code>close(c)</code>
marks the channel as unable to accept more values through a send operation;
values sent to a closed channel are ignored.
sending to or closing a closed channel causes a <a href="#Run_time_panics">run-time panic</a>.
After calling <code>close</code>, and after any previously
sent values have been received, receive operations will return
the zero value for the channel's type without blocking.
Expand Down
36 changes: 12 additions & 24 deletions src/pkg/runtime/chan.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ enum
{
Wclosed = 0x0001, // writer has closed
Rclosed = 0x0002, // reader has seen close
Eincr = 0x0004, // increment errors
Emax = 0x0800, // error limit before throw
};

typedef struct Link Link;
Expand Down Expand Up @@ -151,16 +149,6 @@ runtime·makechan(Type *elem, int64 hint, Hchan *ret)
FLUSH(&ret);
}

static void
incerr(Hchan* c)
{
c->closed += Eincr;
if(c->closed & Emax) {
// Note that channel locks may still be held at this point.
runtime·throw("too many operations on a closed channel");
}
}

/*
* generic single channel send/recv
* if the bool pointer is nil,
Expand Down Expand Up @@ -276,10 +264,8 @@ runtime·chansend(Hchan *c, byte *ep, bool *pres)
return;

closed:
incerr(c);
if(pres != nil)
*pres = true;
runtime·unlock(c);
runtime·panicstring("send on closed channel");
}

void
Expand Down Expand Up @@ -393,7 +379,6 @@ runtime·chanrecv(Hchan* c, byte *ep, bool *pres, bool *closed)
*closed = true;
c->elemalg->copy(c->elemsize, ep, nil);
c->closed |= Rclosed;
incerr(c);
if(pres != nil)
*pres = true;
runtime·unlock(c);
Expand Down Expand Up @@ -863,7 +848,6 @@ runtime·selectgo(Select *sel)
if(cas->u.elemp != nil)
c->elemalg->copy(c->elemsize, cas->u.elemp, nil);
c->closed |= Rclosed;
incerr(c);
goto retc;

syncsend:
Expand All @@ -876,12 +860,6 @@ runtime·selectgo(Select *sel)
gp = sg->g;
gp->param = sg;
runtime·ready(gp);
goto retc;

sclose:
// send on closed channel
incerr(c);
goto retc;

retc:
selunlock(sel);
Expand All @@ -891,6 +869,12 @@ runtime·selectgo(Select *sel)
as = (byte*)&sel + cas->so;
freesel(sel);
*as = true;
return;

sclose:
// send on closed channel
selunlock(sel);
runtime·panicstring("send on closed channel");
}

// closechan(sel *byte);
Expand All @@ -904,7 +888,11 @@ runtime·closechan(Hchan *c)
runtime·gosched();

runtime·lock(c);
incerr(c);
if(c->closed & Wclosed) {
runtime·unlock(c);
runtime·panicstring("close of closed channel");
}

c->closed |= Wclosed;

// release all readers
Expand Down
14 changes: 5 additions & 9 deletions test/chan/select3.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,9 @@ func main() {
}
})

// sending (a small number of times) to a closed channel is not specified
// but the current implementation doesn't block: test that different
// implementations behave the same
testBlock(never, func() {
for i := 0; i < 10; i++ {
closedch <- 7
}
// sending to a closed channel panics.
testPanic(always, func() {
closedch <- 7
})

// receiving from a non-ready channel always blocks
Expand Down Expand Up @@ -189,13 +185,13 @@ func main() {
}
})

// selects with closed channels don't block
// selects with closed channels behave like ordinary operations
testBlock(never, func() {
select {
case <-closedch:
}
})
testBlock(never, func() {
testPanic(always, func() {
select {
case closedch <- 7:
}
Expand Down
18 changes: 12 additions & 6 deletions test/closedchan.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ func (c SChan) Impl() string {
return "(select)"
}

func shouldPanic(f func()) {
defer func() {
if recover() == nil {
panic("did not panic")
}
}()
f()
}

func test1(c Chan) {
// not closed until the close signal (a zero value) has been received.
if c.Closed() {
Expand Down Expand Up @@ -128,18 +137,15 @@ func test1(c Chan) {
}

// send should work with ,ok too: sent a value without blocking, so ok == true.
ok := c.Nbsend(1)
if !ok {
println("test1: send on closed got not ok", c.Impl())
}
shouldPanic(func(){c.Nbsend(1)})

// but the value should have been discarded.
// the value should have been discarded.
if x := c.Recv(); x != 0 {
println("test1: recv on closed got non-zero after send on closed:", x, c.Impl())
}

// similarly Send.
c.Send(2)
shouldPanic(func(){c.Send(2)})
if x := c.Recv(); x != 0 {
println("test1: recv on closed got non-zero after send on closed:", x, c.Impl())
}
Expand Down

0 comments on commit 27c74d3

Please sign in to comment.