// executeFetchContext calls ExecuteFetch() on the given connection, // while respecting Context deadline and cancellation. func (mysqld *Mysqld) executeFetchContext(ctx context.Context, conn dbconnpool.PoolConnection, query string, maxrows int, wantfields bool) (*sqltypes.Result, error) { // Fast fail if context is done. select { case <-ctx.Done(): return nil, ctx.Err() default: } // Execute asynchronously so we can select on both it and the context. var qr *sqltypes.Result var executeErr error done := make(chan struct{}) go func() { defer close(done) qr, executeErr = conn.ExecuteFetch(query, maxrows, wantfields) }() // Wait for either the query or the context to be done. select { case <-done: return qr, executeErr case <-ctx.Done(): // If both are done already, we may end up here anyway because select // chooses among multiple ready channels pseudorandomly. // Check the done channel and prefer that one if it's ready. select { case <-done: return qr, executeErr default: } // The context expired or was cancelled. // Try to kill the connection to effectively cancel the ExecuteFetch(). connID := conn.ID() log.Infof("Mysqld.executeFetchContext(): killing connID %v due to timeout of query: %v", connID, query) if killErr := mysqld.killConnection(connID); killErr != nil { // Log it, but go ahead and wait for the query anyway. log.Warningf("Mysqld.executeFetchContext(): failed to kill connID %v: %v", connID, killErr) } // Wait for the conn.ExecuteFetch() call to return. <-done // Close the connection. Upon Recycle() it will be thrown out. conn.Close() // ExecuteFetch() may have succeeded before we tried to kill it. // If ExecuteFetch() had returned because we cancelled it, // then executeErr would be an error like "MySQL has gone away". if executeErr == nil { return qr, executeErr } return nil, ctx.Err() } }