Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Make a closed SSH server stop before it returns

786d7ce1a17ddb2224cefedbe73564cc8165ca32 · Keenan Nemetz · 2d ago · parent f642e0aa

Make a closed SSH server stop before it returns

CI caught the new concurrency test failing on a temporary directory that was not
empty: a connection handler was still finishing a disconnect when the test harness
removed the directory it had handed the server. The test was the trigger, not the
cause — Close() only closed the listener and left every handler and session
goroutine running.

Close now ends the connections it is serving and waits for their goroutines, so
"closed" means nothing this server started is still writing. The wait is bounded
(10s) and reported, because a git process stuck on a dead filesystem must not hold
up a shutdown. A parked lease answers on Close as well, rather than making the
wait sit out its 25-second window.

Refs: 01M6SQ7WC68KGP57MN1DS3DFPQ (#220)

server/ssh/close_test.go +36

@@ -0,0 +1,36 @@
1 package ssh
2
3 import (
4 "testing"
5 "time"
6 )
7
8 // A closed server has nothing running. The test harness removes its temporary
9 // directory as the last cleanup step, and a handler that is still finishing a
10 // disconnect — writing the last row, dropping the session — would race that removal
11 // and leave the directory non-empty. So Close ends the connections it is serving and
12 // waits, and this is the contract that makes it worth doing.
13 func TestClosingTheServerEndsItsConnections(t *testing.T) {
14 t.Parallel()
15 h := newHarness(t)
16 c := h.runner(t, h.keyPath)
17 if rep := c.call(runnerRequest{Op: "ping"}); rep.Error != "" {
18 t.Fatal(rep.Error)
19 }
20
21 if err := h.srv.Close(); err != nil {
22 t.Fatalf("close: %v", err)
23 }
24
25 // The session ends because the server ended the connection under it. Waiting for
26 // it is the assertion: a server that let a handler outlive Close would leave this
27 // hanging, and that handler would be free to write into the directory the caller
28 // is about to remove. The error itself is not interesting — EOF is the answer.
29 waited := make(chan error, 1)
30 go func() { waited <- c.sess.Wait() }()
31 select {
32 case <-waited:
33 case <-time.After(5 * time.Second):
34 t.Fatal("the session stayed open after the server closed")
35 }
36 }