Skip to content

Query parser not thread-safe: concurrent pgx connections cause syntax errors #2581

@johannesdb

Description

@johannesdb

Summary

DoltgreSQL 0.56.1 returns syntax error responses when multiple pgx pool connections execute queries concurrently. The error rate scales linearly with pool size — 0% at MaxConns=1, ~50% at MaxConns=2, ~87% at MaxConns=8.

The error messages contain MySQL error codes (errno 1105, sqlstate HY000) leaking through the PostgreSQL wire protocol, suggesting the issue is in go-mysql-server's query processing layer.

Environment

  • DoltgreSQL: 0.56.1 (dolthub/doltgresql:0.56.1 Docker image)
  • Client: Go 1.26, jackc/pgx/v5 with pgxpool
  • OS: macOS (client), Linux (Docker container)

Reproduction

Minimal Go test that reproduces the issue:

func TestConcurrencySyntaxError(t *testing.T) {
    ctx := context.Background()
    cfg, _ := pgxpool.ParseConfig("postgres://postgres:password@localhost:5432/testdb?sslmode=disable")
    cfg.MaxConns = 4

    pool, err := pgxpool.NewWithConfig(ctx, cfg)
    if err != nil {
        t.Skip("DoltgreSQL not available")
    }
    defer pool.Close()

    // Seed data
    pool.Exec(ctx, `CREATE TABLE IF NOT EXISTS users (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT UNIQUE, name TEXT, active BOOLEAN DEFAULT TRUE)`)
    pool.Exec(ctx, `INSERT INTO users (id, email, name) VALUES ('a0000000-0000-0000-0000-000000000001', '[email protected]', 'Test') ON CONFLICT DO NOTHING`)
    pool.Exec(ctx, `CREATE TABLE IF NOT EXISTS sessions (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), token_hash TEXT, user_id UUID, expires_at TIMESTAMPTZ)`)
    pool.Exec(ctx, `INSERT INTO sessions (token_hash, user_id, expires_at) VALUES ('testhash', 'a0000000-0000-0000-0000-000000000001', NOW() + INTERVAL '1 hour') ON CONFLICT DO NOTHING`)

    var syntaxErrors, total atomic.Int64
    var wg sync.WaitGroup

    for w := 0; w < 16; w++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for i := 0; i < 200; i++ {
                total.Add(1)
                var id string
                err := pool.QueryRow(ctx, `
                    UPDATE sessions SET expires_at = NOW() + INTERVAL '30 days'
                    WHERE token_hash = 'testhash' AND expires_at > NOW()
                    RETURNING id
                `).Scan(&id)
                if err != nil && strings.Contains(err.Error(), "syntax error") {
                    syntaxErrors.Add(1)
                }
            }
        }()
    }
    wg.Wait()

    t.Logf("%d/%d queries returned syntax errors", syntaxErrors.Load(), total.Load())
    // Expect ~50-87% syntax errors with MaxConns >= 2
}

Results (DoltgreSQL 0.56.1)

MaxConns Total Queries Syntax Errors Error Rate
1 200 0 0%
2 400 198 49.5%
3 600 393 65.5%
4 800 596 74.5%
5 1000 798 79.8%
8 1600 1397 87.3%

Error message

ERROR: at or near "token_hash": syntax error (errno 1105) (sqlstate HY000) (SQLSTATE XX000)

Note the MySQL-style errno 1105 and sqlstate HY000 leaking through the PostgreSQL protocol. The column name in the error (token_hash) comes from the WHERE clause of the concurrent query, suggesting the parser is seeing partial SQL from another connection's query.

Workaround

Setting MaxConns = 1 on the pgxpool config eliminates the error entirely, but at the cost of serializing all queries (no concurrency).

Impact

This affects any application using connection pooling with DoltgreSQL. The default pgxpool MaxConns is max(4, runtime.NumCPU()), which will trigger the bug on any multi-core machine. Real-world symptoms include:

  • Auth middleware returning "syntax error" → user redirected to login
  • 500 errors on page loads with concurrent HTMX requests
  • Service crash after sustained concurrent load

Possibly related

The go-mysql-server README notes: "To avoid concurrency issues, limit DDL and DML statements to a single goroutine." — this constraint appears to extend to DoltgreSQL server-mode as well, despite the documentation stating concurrent connections are supported.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions