jok-carbonarc commented on PR #2581:
URL: https://github.com/apache/age/pull/2581#issuecomment-6450541137

   A follow-up for the same `PG18` batch: please also consider the **detached 
graph-context build from #2561** (`3eeb20bd`). On `PG18` it fixes a silent 
wrong-result bug.
   
   **The bug (PG18 at `e43dc1a`, 1.8.0; measured on our build of it, which adds 
local parser patches, with and without #2581. `manage_GRAPH_global_contexts()` 
is unchanged by them):**
   - `manage_GRAPH_global_contexts()` links a new `GRAPH_global_context` into 
`global_graph_contexts` before `load_GRAPH_global_hashtables()` runs.
   - If the load is interrupted (`statement_timeout`, a cancel), the 
half-loaded context stays on the list.
   - Once the graph's version counter is > 0, `is_ggctx_invalid()` keeps that 
context valid.
   - So every later VLE in that backend reads a partial graph and returns 
**wrong results with no error**, until the graph is written or the connection 
closes.
   - On `master`, #2561 builds the context detached and frees it on failure, 
which fixes this as a side effect of the RLS work.
   
   **Reproduction** (PG 18.6, `shared_preload_libraries = 'age'`, fresh server):
   
   ```sql
   CREATE EXTENSION age; SET search_path = ag_catalog, "$user", public;
   SELECT create_graph('t2f');
   SELECT * FROM cypher('t2f', $$ UNWIND range(0, 199999) AS i CREATE (:N {id: 
i}) $$) AS (a agtype);
   SELECT * FROM cypher('t2f', $$ CREATE (:L0)-[:L]->(:L0) $$) AS (a agtype);   
       -- creates label L
   SELECT * FROM cypher('t2f', $$ MATCH (x:L0) DETACH DELETE x $$) AS (a 
agtype);
   CREATE TEMP TABLE v AS SELECT id, (row_number() OVER (ORDER BY id) - 1)::int 
AS k FROM t2f."N";
   INSERT INTO t2f."L" (start_id, end_id, properties)
     SELECT a.id, b.id, '{}'::agtype FROM generate_series(0::bigint, 799999) j
     JOIN v a ON a.k = (j * 7919) % 200000 JOIN v b ON b.k = (j * 104729 + 13) 
% 200000;
   
   -- then, on a NEW connection:
   SET search_path = ag_catalog, "$user", public;
   SET statement_timeout = '150ms';   -- must land inside the first VLE's graph 
load (about 300 ms here)
   SELECT count(*) FROM cypher('t2f', $$ MATCH (a:N)-[:L*1..1]->(b:N) RETURN b 
$$) AS (b agtype);   -- canceled
   SET statement_timeout = 0;
   SELECT count(*) FROM cypher('t2f', $$ MATCH (a:N)-[:L*1..1]->(b:N) RETURN b 
$$) AS (b agtype);   -- should be 800000
   ```
   
   The second count is wrong on that connection, and right on a fresh one. Four 
runs, each a fresh server, on our builds with and without #2581:
   
   | timeout (ms) | none | 20 | 50 | 100 | 150 | 200 | 250 | 300 | 600 | 1000 |
   |---|---|---|---|---|---|---|---|---|---|---|
   | without the hunk | 800000 | 800000 | **0** | **42022–126206** | 
**89896–222904** | **280976–374952** | **408728–445720** | **445672–528224** (3 
of 4) | 800000 | 800000 |
   | with the hunk below | 800000 at every timeout, 2 runs | | | | | | | | | |
   
   **The hunk** (only #2561's detached build and free-on-failure, without the 
RLS changes):
   - applied on `PG18` (with #2581), it gives 800000 at every timeout (20 of 
20);
   - our build with it passes `make installcheck`, 44 of 44;
   - after 10 canceled loads on one backend, `pg_backend_memory_contexts` shows 
no AGE edge table left, and the backend's memory stays flat.
   
   ```diff
   diff --git a/src/backend/utils/adt/age_global_graph.c 
b/src/backend/utils/adt/age_global_graph.c
   --- a/src/backend/utils/adt/age_global_graph.c
   +++ b/src/backend/utils/adt/age_global_graph.c
   @@ -1105,20 +1105,16 @@ GRAPH_global_context 
*manage_GRAPH_global_contexts(char *graph_name,
            curr_ggctx = curr_ggctx->next;
        }
    
   -    /* otherwise, we need to create one and possibly attach it */
   +    /*
   +     * Otherwise, we need to build one. Build it DETACHED from the list and
   +     * attach it only after a successful load (upstream #2561): a load 
stopped
   +     * by an error - statement_timeout, a cancel - would otherwise leave a
   +     * half-loaded context on the list, and is_ggctx_invalid() keeps judging
   +     * it valid while the graph's version counter is unchanged, so every 
later
   +     * VLE on this backend would read a partial graph without an error.
   +     */
        new_ggctx = palloc0(sizeof(GRAPH_global_context));
   -
   -    if (global_graph_contexts != NULL)
   -    {
   -        new_ggctx->next = global_graph_contexts;
   -    }
   -    else
   -    {
   -        new_ggctx->next = NULL;
   -    }
   -
   -    /* set the global context variable */
   -    global_graph_contexts = new_ggctx;
   +    new_ggctx->next = NULL;
    
        /* set the graph name and oid */
        new_ggctx->graph_name = pstrdup(graph_name);
   @@ -1135,10 +1131,31 @@ GRAPH_global_context 
*manage_GRAPH_global_contexts(char *graph_name,
        /* initialize our vertices list */
        new_ggctx->vertices = NULL;
    
   -    /* build the hashtables for this graph */
   -    create_GRAPH_global_hashtables(new_ggctx);
   -    load_GRAPH_global_hashtables(new_ggctx);
   -    freeze_GRAPH_global_hashtables(new_ggctx);
   +    /* build the hashtables for this graph (detached from the list) */
   +    PG_TRY();
   +    {
   +        create_GRAPH_global_hashtables(new_ggctx);
   +        load_GRAPH_global_hashtables(new_ggctx);
   +        freeze_GRAPH_global_hashtables(new_ggctx);
   +    }
   +    PG_CATCH();
   +    {
   +        /*
   +         * Free the partially-built context and re-throw. This is safe on a
   +         * partial build: a vertex id is added to the vertices list only 
after
   +         * its vertex_hashtable entry exists, the edge arrays are freed only
   +         * when allocated, and the edge_table lives in its own memory 
context;
   +         * nothing used here re-throws.
   +         */
   +        (void) free_specific_GRAPH_global_context(new_ggctx);
   +        MemoryContextSwitchTo(oldctx);
   +        PG_RE_THROW();
   +    }
   +    PG_END_TRY();
   +
   +    /* attach the fully-built context to the list */
   +    new_ggctx->next = global_graph_contexts;
   +    global_graph_contexts = new_ggctx;
    
    
        /* switch back to the previous memory context */
   ```
   
   `free_specific_GRAPH_global_context()` is safe on a partially built context 
on `PG18` too:
   - a vertex id joins the `vertices` list only after its hash entry exists;
   - `vea_free()` checks for NULL;
   - the edge table lives in its own memory context.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to