mirror of https://github.com/fluffle/goirc
Make Conn's Shutdown method public.
This commit is contained in:
parent
a2223065b2
commit
734da36009
|
@ -4,10 +4,11 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
irc "github.com/fluffle/goirc/client"
|
|
||||||
"github.com/fluffle/goirc/logging/glog"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
irc "github.com/fluffle/goirc/client"
|
||||||
|
"github.com/fluffle/goirc/logging/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var host *string = flag.String("host", "irc.freenode.net", "IRC server")
|
var host *string = flag.String("host", "irc.freenode.net", "IRC server")
|
||||||
|
@ -70,6 +71,9 @@ func main() {
|
||||||
case cmd[1] == 'q':
|
case cmd[1] == 'q':
|
||||||
reallyquit = true
|
reallyquit = true
|
||||||
c.Quit(cmd[idx+1 : len(cmd)])
|
c.Quit(cmd[idx+1 : len(cmd)])
|
||||||
|
case cmd[1] == 's':
|
||||||
|
reallyquit = true
|
||||||
|
c.Shutdown()
|
||||||
case cmd[1] == 'j':
|
case cmd[1] == 'j':
|
||||||
c.Join(cmd[idx+1 : len(cmd)])
|
c.Join(cmd[idx+1 : len(cmd)])
|
||||||
case cmd[1] == 'p':
|
case cmd[1] == 'p':
|
||||||
|
|
|
@ -386,9 +386,9 @@ func (conn *Conn) send() {
|
||||||
case line := <-conn.out:
|
case line := <-conn.out:
|
||||||
if err := conn.write(line); err != nil {
|
if err := conn.write(line); err != nil {
|
||||||
logging.Error("irc.send(): %s", err.Error())
|
logging.Error("irc.send(): %s", err.Error())
|
||||||
// We can't defer this, because shutdown() waits for it.
|
// We can't defer this, because Shutdown() waits for it.
|
||||||
conn.wg.Done()
|
conn.wg.Done()
|
||||||
conn.shutdown()
|
conn.Shutdown()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-conn.die:
|
case <-conn.die:
|
||||||
|
@ -409,9 +409,9 @@ func (conn *Conn) recv() {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
logging.Error("irc.recv(): %s", err.Error())
|
logging.Error("irc.recv(): %s", err.Error())
|
||||||
}
|
}
|
||||||
// We can't defer this, because shutdown() waits for it.
|
// We can't defer this, because Shutdown() waits for it.
|
||||||
conn.wg.Done()
|
conn.wg.Done()
|
||||||
conn.shutdown()
|
conn.Shutdown()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s = strings.Trim(s, "\r\n")
|
s = strings.Trim(s, "\r\n")
|
||||||
|
@ -503,17 +503,18 @@ func (conn *Conn) rateLimit(chars int) time.Duration {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdown tears down all connection-related state. It is called when either
|
// Shutdown tears down all connection-related state. It is called when either
|
||||||
// the sending or receiving goroutines encounter an error.
|
// the sending or receiving goroutines encounter an error.
|
||||||
func (conn *Conn) shutdown() {
|
// It may also be used to forcibly shut down the connection to the server.
|
||||||
// Guard against double-call of shutdown() if we get an error in send()
|
func (conn *Conn) Shutdown() {
|
||||||
|
// Guard against double-call of Shutdown() if we get an error in send()
|
||||||
// as calling sock.Close() will cause recv() to receive EOF in readstring()
|
// as calling sock.Close() will cause recv() to receive EOF in readstring()
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
if !conn.connected {
|
if !conn.connected {
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logging.Info("irc.shutdown(): Disconnected from server.")
|
logging.Info("irc.Shutdown(): Disconnected from server.")
|
||||||
conn.connected = false
|
conn.connected = false
|
||||||
conn.sock.Close()
|
conn.sock.Close()
|
||||||
close(conn.die)
|
close(conn.die)
|
||||||
|
|
|
@ -70,11 +70,11 @@ func setUp(t *testing.T, start ...bool) (*Conn, *testState) {
|
||||||
|
|
||||||
func (s *testState) tearDown() {
|
func (s *testState) tearDown() {
|
||||||
s.nc.ExpectNothing()
|
s.nc.ExpectNothing()
|
||||||
s.c.shutdown()
|
s.c.Shutdown()
|
||||||
s.ctrl.Finish()
|
s.ctrl.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Practically the same as the above test, but shutdown is called implicitly
|
// Practically the same as the above test, but Shutdown is called implicitly
|
||||||
// by recv() getting an EOF from the mock connection.
|
// by recv() getting an EOF from the mock connection.
|
||||||
func TestEOF(t *testing.T) {
|
func TestEOF(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
|
@ -197,7 +197,7 @@ func TestSendExitsOnDie(t *testing.T) {
|
||||||
// Now, use the control channel to exit send and kill the goroutine.
|
// Now, use the control channel to exit send and kill the goroutine.
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally Shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
exited.assertNotCalled("Exited before signal sent.")
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
|
@ -230,7 +230,7 @@ func TestSendExitsOnWriteError(t *testing.T) {
|
||||||
s.nc.Expect("SENT AFTER START")
|
s.nc.Expect("SENT AFTER START")
|
||||||
|
|
||||||
// Now, close the underlying socket to cause write() to return an error.
|
// Now, close the underlying socket to cause write() to return an error.
|
||||||
// This will call shutdown() => a call to st.Wipe() will happen.
|
// This will call Shutdown() => a call to st.Wipe() will happen.
|
||||||
exited.assertNotCalled("Exited before signal sent.")
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
s.nc.Close()
|
s.nc.Close()
|
||||||
// Sending more on c.out shouldn't reach the network, but we need to send
|
// Sending more on c.out shouldn't reach the network, but we need to send
|
||||||
|
@ -244,8 +244,8 @@ func TestSendDeadlockOnFullBuffer(t *testing.T) {
|
||||||
// Passing a second value to setUp stops goroutines from starting
|
// Passing a second value to setUp stops goroutines from starting
|
||||||
c, s := setUp(t, false)
|
c, s := setUp(t, false)
|
||||||
// We can't use tearDown here because we're testing a deadlock condition
|
// We can't use tearDown here because we're testing a deadlock condition
|
||||||
// and if tearDown tries to call shutdown() it will deadlock some more
|
// and if tearDown tries to call Shutdown() it will deadlock some more
|
||||||
// because send() is holding the conn mutex via shutdown() already.
|
// because send() is holding the conn mutex via Shutdown() already.
|
||||||
defer s.ctrl.Finish()
|
defer s.ctrl.Finish()
|
||||||
|
|
||||||
// We want to test that the a goroutine calling send will exit correctly.
|
// We want to test that the a goroutine calling send will exit correctly.
|
||||||
|
@ -257,7 +257,7 @@ func TestSendDeadlockOnFullBuffer(t *testing.T) {
|
||||||
// The deadlock arises when a handler being called from conn.dispatch() in
|
// The deadlock arises when a handler being called from conn.dispatch() in
|
||||||
// runLoop() tries to write to conn.out to send a message back to the IRC
|
// runLoop() tries to write to conn.out to send a message back to the IRC
|
||||||
// server, but the buffer is full. If at the same time send() is
|
// server, but the buffer is full. If at the same time send() is
|
||||||
// calling conn.shutdown() and waiting in there for runLoop() to call
|
// calling conn.Shutdown() and waiting in there for runLoop() to call
|
||||||
// conn.wg.Done(), it will not empty the buffer of conn.out => deadlock.
|
// conn.wg.Done(), it will not empty the buffer of conn.out => deadlock.
|
||||||
//
|
//
|
||||||
// We simulate this by artifically filling conn.out. We must use a
|
// We simulate this by artifically filling conn.out. We must use a
|
||||||
|
@ -282,7 +282,7 @@ func TestSendDeadlockOnFullBuffer(t *testing.T) {
|
||||||
// At this point the handler should be blocked on a write to conn.out,
|
// At this point the handler should be blocked on a write to conn.out,
|
||||||
// preventing runLoop from looping and thus noticing conn.die is closed.
|
// preventing runLoop from looping and thus noticing conn.die is closed.
|
||||||
//
|
//
|
||||||
// The next part is to force send() to call conn.shutdown(), which can
|
// The next part is to force send() to call conn.Shutdown(), which can
|
||||||
// be done by closing the fake net.Conn so that it returns an error on
|
// be done by closing the fake net.Conn so that it returns an error on
|
||||||
// calls to Write():
|
// calls to Write():
|
||||||
s.nc.ExpectNothing()
|
s.nc.ExpectNothing()
|
||||||
|
@ -290,7 +290,7 @@ func TestSendDeadlockOnFullBuffer(t *testing.T) {
|
||||||
|
|
||||||
// Now when send is started it will read one line from conn.out and try
|
// Now when send is started it will read one line from conn.out and try
|
||||||
// to write it to the socket. It should immediately receive an error and
|
// to write it to the socket. It should immediately receive an error and
|
||||||
// call conn.shutdown(), triggering the deadlock as it waits forever for
|
// call conn.Shutdown(), triggering the deadlock as it waits forever for
|
||||||
// runLoop to call conn.wg.Done.
|
// runLoop to call conn.wg.Done.
|
||||||
go func() {
|
go func() {
|
||||||
c.send()
|
c.send()
|
||||||
|
@ -301,8 +301,8 @@ func TestSendDeadlockOnFullBuffer(t *testing.T) {
|
||||||
<-time.After(time.Millisecond)
|
<-time.After(time.Millisecond)
|
||||||
|
|
||||||
// Verify that the connection no longer thinks it's connected, i.e.
|
// Verify that the connection no longer thinks it's connected, i.e.
|
||||||
// conn.shutdown() has definitely been called. We can't call
|
// conn.Shutdown() has definitely been called. We can't call
|
||||||
// conn.Connected() here because conn.shutdown() holds the mutex.
|
// conn.Connected() here because conn.Shutdown() holds the mutex.
|
||||||
if c.connected {
|
if c.connected {
|
||||||
t.Errorf("Conn still thinks it's connected to the server.")
|
t.Errorf("Conn still thinks it's connected to the server.")
|
||||||
}
|
}
|
||||||
|
@ -441,7 +441,7 @@ func TestPing(t *testing.T) {
|
||||||
// Now kill the ping loop.
|
// Now kill the ping loop.
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally Shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
exited.assertNotCalled("Exited before signal sent.")
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
|
@ -493,7 +493,7 @@ func TestRunLoop(t *testing.T) {
|
||||||
// Now, use the control channel to exit send and kill the goroutine.
|
// Now, use the control channel to exit send and kill the goroutine.
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally Shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
exited.assertNotCalled("Exited before signal sent.")
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
|
|
Loading…
Reference in New Issue