Skip to content

Commit 623630f

Browse files
committed
sql: fix minor bug around disabling portal pausability
I've been reviewing the pausable portal code, and I think I found a minor bug that doesn't actually appear to have any consequences. What happens is that if we created a pausable portal, but then it's executed without a limit (i.e. `MaxRows` is not set on `Execute`), then we disable pausability since the portal will run to completion. We do that by modifying a _copy_ of `PreparedPortal` struct that is used throughout the execution, yet we don't update the map that stores all portals. Note that other nested things (like `portalPauseInfo`) are stored by reference, so modifications to them are reflected in the copy stored in the map, so I think things just happen to work out. Still, it seems prudent to update the copy that we store in the map with the fact that pausability was disabled. A similar issue is present when we revoke the portal pausability - we modify the copy of PreparedPortal and don't update the map. Additionally, here we explicitly reset `planner.pausablePortal` to `nil` when revoking pausability. I audited all cleanup functions, and they should behave correctly: namely, we'll have nil'ed out `pauseInfo`, so the portal won't be considered pausable, and the cleanup will be performed right away, avoiding the pausauble portal cleanup stacks. Release note: None
1 parent 7536bef commit 623630f

File tree

3 files changed

+17
-2
lines changed

3 files changed

+17
-2
lines changed

pkg/sql/conn_executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2455,7 +2455,7 @@ func (ex *connExecutor) execCmd() (retErr error) {
24552455
// If this is the first-time execution of a portal without a limit set,
24562456
// it means all rows will be exhausted, so no need to pause this portal.
24572457
if tcmd.Limit == 0 && portal.pauseInfo != nil && portal.pauseInfo.curRes == nil {
2458-
portal.pauseInfo = nil
2458+
ex.disablePortalPausability(&portal)
24592459
}
24602460

24612461
stmtRes := ex.clientComm.CreateStatementResult(

pkg/sql/conn_executor_exec.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2765,7 +2765,8 @@ func (ex *connExecutor) dispatchToExecutionEngine(
27652765
}
27662766
// This stmt is not supported via the pausable portals model
27672767
// - set it back to an un-pausable (normal) portal.
2768-
planner.pausablePortal.pauseInfo = nil
2768+
ex.disablePortalPausability(planner.pausablePortal)
2769+
planner.pausablePortal = nil
27692770
err = res.RevokePortalPausability()
27702771
// If this plan is a transaction control statement, we don't
27712772
// even execute it but just early exit.

pkg/sql/prepared_stmt.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,17 @@ const (
8282
// PreparedPortal is a PreparedStatement that has been bound with query
8383
// arguments.
8484
type PreparedPortal struct {
85+
// Fields below are initialized when creating the PreparedPortal and aren't
86+
// modified later.
8587
Name string
8688
Stmt *prep.Statement
8789
Qargs tree.QueryArguments
8890

8991
// OutFormats contains the requested formats for the output columns.
9092
OutFormats []pgwirebase.FormatCode
9193

94+
// Fields below might be updated throughout the PreparedPortal's lifecycle.
95+
9296
// exhausted tracks whether this portal has already been fully exhausted,
9397
// meaning that any additional attempts to execute it should return no
9498
// rows.
@@ -141,6 +145,16 @@ func (ex *connExecutor) makePreparedPortal(
141145
return portal, portal.accountForCopy(ctx, &ex.extraTxnState.prepStmtsNamespaceMemAcc, name)
142146
}
143147

148+
func (ex *connExecutor) disablePortalPausability(portal *PreparedPortal) {
149+
portal.portalPausablity = PortalPausabilityDisabled
150+
portal.pauseInfo = nil
151+
// Since the PreparedPortal is stored by value in the map, we need to
152+
// explicitly update it. (Note that PreparedPortal.pauseInfo is stored by
153+
// pointer, so modifications to portalPauseInfo will be reflected in the map
154+
// automatically.)
155+
ex.extraTxnState.prepStmtsNamespace.portals[portal.Name] = *portal
156+
}
157+
144158
// accountForCopy updates the state to account for the copy of the
145159
// PreparedPortal (p is the copy).
146160
func (p *PreparedPortal) accountForCopy(

0 commit comments

Comments
 (0)