@@ -299,13 +299,11 @@ func (c *Client) Start(ctx context.Context, options StartOptions) (wait func() e
299299 return func () error { return <- exited }, nil
300300}
301301
302- // waitForPort polls until the runtime inside the container answers .
302+ // waitForPort polls until the published port accepts a connection .
303303//
304- // A bare TCP connect is not enough: docker's port proxy accepts connections
305- // while the runtime behind it is still unpacking code, so `run` announced
306- // success several seconds early. Any reply counts, including a 500 -- only a
307- // connection that closes without one is not-ready-yet. 100 attempts at 100ms
308- // covers a cold start without hanging a broken one forever.
304+ // Readiness must stop at the transport boundary. Sending an HTTP request here
305+ // executes the user's function before `run` has started and rejects valid
306+ // functions that do not answer that synthetic request.
309307func waitForPort (ctx context.Context , port int ) error {
310308 address := net .JoinHostPort ("127.0.0.1" , strconv .Itoa (port ))
311309
@@ -317,8 +315,10 @@ func waitForPort(ctx context.Context, port int) error {
317315 default :
318316 }
319317
320- err := probe ( address )
318+ connection , err := net . DialTimeout ( "tcp" , address , time . Second )
321319 if err == nil {
320+ connection .Close ()
321+
322322 return nil
323323 }
324324 lastErr = err
@@ -329,34 +329,6 @@ func waitForPort(ctx context.Context, port int) error {
329329 return fmt .Errorf ("timed out waiting for port %d: %w" , port , lastErr )
330330}
331331
332- // probe sends the smallest well-formed request that gets an answer.
333- func probe (address string ) error {
334- connection , err := net .DialTimeout ("tcp" , address , time .Second )
335- if err != nil {
336- return err
337- }
338- defer connection .Close ()
339-
340- if err := connection .SetDeadline (time .Now ().Add (time .Second )); err != nil {
341- return err
342- }
343-
344- // HTTP/1.0 so the server closes rather than holding the connection open for
345- // a keep-alive that nothing here will use.
346- if _ , err := connection .Write ([]byte ("GET / HTTP/1.0\r \n \r \n " )); err != nil {
347- return err
348- }
349-
350- // One byte is the whole signal. Reading the status line would mean parsing
351- // it, and the reply's CONTENT is not what is being checked.
352- answer := make ([]byte , 1 )
353- if _ , err := connection .Read (answer ); err != nil {
354- return fmt .Errorf ("no reply from the runtime: %w" , err )
355- }
356-
357- return nil
358- }
359-
360332// PortAvailable reports whether a port can be published.
361333//
362334// Both loopback addresses are checked, because `localhost` is not one address: a
0 commit comments