@@ -1886,6 +1886,54 @@ var _ = Describe("manger.Manager", func() {
1886
1886
Eventually (func () error { return goleak .Find (currentGRs ) }).Should (Succeed ())
1887
1887
})
1888
1888
1889
+ It ("should not leak goroutines when a runnable returns error slowly after being signaled to stop" , func () {
1890
+ // This test reproduces the race condition where the manager's Start method
1891
+ // exits due to context cancellation, leaving no one to drain errChan
1892
+
1893
+ currentGRs := goleak .IgnoreCurrent ()
1894
+
1895
+ // Create manager with a very short graceful shutdown timeout to reliablytrigger the race condition
1896
+ shortGracefulShutdownTimeout := 10 * time .Millisecond
1897
+ m , err := New (cfg , Options {
1898
+ GracefulShutdownTimeout : & shortGracefulShutdownTimeout ,
1899
+ })
1900
+ Expect (err ).NotTo (HaveOccurred ())
1901
+
1902
+ // Add the slow runnable that will return an error after some delay
1903
+ for i := 0 ; i < 3 ; i ++ {
1904
+ slowRunnable := RunnableFunc (func (c context.Context ) error {
1905
+ <- c .Done ()
1906
+
1907
+ // Simulate some work that delays the error from being returned
1908
+ // Choosing a large delay to reliably trigger the race condition
1909
+ time .Sleep (100 * time .Millisecond )
1910
+
1911
+ // This simulates the race condition where runnables try to send
1912
+ // errors after the manager has stopped reading from errChan
1913
+ return errors .New ("slow runnable error" )
1914
+ })
1915
+
1916
+ Expect (m .Add (slowRunnable )).To (Succeed ())
1917
+ }
1918
+
1919
+ ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
1920
+ defer cancel ()
1921
+ go func () {
1922
+ defer GinkgoRecover ()
1923
+ m .Start (ctx )
1924
+ }()
1925
+
1926
+ // Wait for context to be cancelled
1927
+ <- ctx .Done ()
1928
+
1929
+ // Give time for any leaks to become apparent. This makes sure that we don't false alarm on go routine leaks because runnables are still running.
1930
+ time .Sleep (300 * time .Millisecond )
1931
+
1932
+ // force-close keep-alive connections
1933
+ clientTransport .CloseIdleConnections ()
1934
+ Eventually (func () error { return goleak .Find (currentGRs ) }).Should (Succeed ())
1935
+ })
1936
+
1889
1937
It ("should provide a function to get the Config" , func () {
1890
1938
m , err := New (cfg , Options {})
1891
1939
Expect (err ).NotTo (HaveOccurred ())
0 commit comments