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 | } | ||