Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2c7cf89
test: native_fold_deferral gains its pytest twin, and a mirror copied…
jdatcmd Oct 22, 2026
3d1c98a
test: native_lazy_slot gains its pytest twin, and a heap mirror copie…
jdatcmd Oct 22, 2026
e97df71
test: native_metadata_flush gains its pytest twin, and a witness that…
jdatcmd Oct 22, 2026
1391695
test: native_late_materialization gains its pytest twin, and an unrea…
jdatcmd Oct 22, 2026
c73e27a
test: native_late_materialization's fixture premises read the heap, a…
jdatcmd Oct 22, 2026
8e33eb0
test: native_reclaim_reconcile gains its pytest twin, and a reconcile…
jdatcmd Oct 22, 2026
4529243
test: native_reclaim_reconcile's free-range check is not a premise, a…
jdatcmd Oct 22, 2026
f8dea09
test: native_page_offset_bound gains its pytest twin, and a crash no …
jdatcmd Oct 22, 2026
d03573c
test: native_param_pushdown gains its pytest twin, and a frozen corre…
jdatcmd Oct 22, 2026
9d426d1
test: native_sort_by gains its pytest twin, and a sort that kept the …
jdatcmd Oct 22, 2026
19c0050
test: native_rewrite gains its pytest twin, and a vacuum that left th…
jdatcmd Oct 22, 2026
498a640
test: native_zonemap gains its pytest twin, and a bound that kept a v…
jdatcmd Oct 22, 2026
e50602a
test: the native_zonemap twin fails its zone-map readings when the st…
jdatcmd Oct 22, 2026
d7a5a2f
test: native_zonemap_session gains its pytest twin, and a session sha…
jdatcmd Oct 22, 2026
d95d665
test: native_zonemap_session's group-count check is not a premise, an…
jdatcmd Oct 22, 2026
8ba352b
test: native_zonemap_narrow gains its pytest twin, and a scan that ne…
jdatcmd Oct 22, 2026
f2ddc9f
test: native_vecdecode gains its pytest twin, and a fold that reads n…
jdatcmd Oct 22, 2026
bece7a3
test: native_vecdecode's plan and layout checks are not premises; nos…
jdatcmd Oct 22, 2026
12751fa
test: native_parquet_fieldid gains its pytest twin, and a refusal mus…
jdatcmd Oct 22, 2026
5efa5e0
test: native_parquet_codecs gains its pytest twin, and an oracle read…
jdatcmd Oct 22, 2026
674c2fe
test: native_parquet_units gains its pytest twin, and a pushdown orac…
jdatcmd Oct 22, 2026
d0d69d6
test: native_parquet_projection gains its pytest twin, and a decoder …
jdatcmd Oct 22, 2026
1ceef07
test: native_parquet_hardening gains its pytest twin, and another gua…
jdatcmd Oct 22, 2026
8948280
test: native_parquet_stack gains its pytest twin, and each footer-rec…
jdatcmd Oct 22, 2026
29f6f5f
test: native_parquet_stack's survival checks read the crash count, an…
jdatcmd Oct 22, 2026
124a5a6
test: native_parquet_streaming gains its pytest twin, and a crashed b…
jdatcmd Oct 22, 2026
47db553
test: native_parquet_partition gains its pytest twin, and a refusal m…
jdatcmd Oct 22, 2026
a239c9a
test: native_parquet_fdw gains its pytest twin, and a scan that prune…
jdatcmd Oct 22, 2026
3624f4c
test: native_parquet_fdw's skipped-scan count runs the query that ski…
jdatcmd Oct 22, 2026
972195a
test: native_read_parquet gains its pytest twin, and a round trip tha…
jdatcmd Oct 22, 2026
ad99b78
test: native_read_parquet's parser-only check is a premise, and the r…
jdatcmd Oct 22, 2026
329fb42
test: objstore_listing pytest twin, each listing refusal by its own g…
jdatcmd Oct 22, 2026
8a9acfe
test: pytest twin of native_rewrite_conc.sh, and close its two holes …
jdatcmd Oct 22, 2026
907c768
test: port objstore_addressing to a pytest twin, and close its holes …
jdatcmd Oct 22, 2026
fffe056
test: objstore_stash_recovery gains its pytest twin, and a guard that…
jdatcmd Oct 22, 2026
cac6bc2
test: lib.sh pins its socket directory, so a non-postgres run of a sh…
jdatcmd Oct 23, 2026
e8453a7
test: objstore_s3_read twin, and every refusal names its cause (#1341)
jdatcmd Oct 22, 2026
b7ff5e5
test: objstore_tls_read gains its pytest twin, and every TLS refusal …
jdatcmd Oct 22, 2026
95193b3
test: port objstore_http_read to a pytest twin, pin each refusal's gu…
jdatcmd Oct 22, 2026
1cea827
test: objstore_sink_write pytest twin, multipart read as S3 does it (…
jdatcmd Oct 22, 2026
9b1630c
fix: a cancelled remote parallel export aborts its workers' uploads (…
jdatcmd Oct 22, 2026
d37d54e
fix: an object-store wait slice times out, so a silent store cannot h…
jdatcmd Oct 22, 2026
ba82bda
test: native_fetch_interrupt gains its pytest twin, and a guard comme…
jdatcmd Oct 22, 2026
2502756
test: objstore_module gains its pytest twin, and a module that drops …
jdatcmd Oct 22, 2026
11d00f2
test: phase2 gains its pytest twin, and a codec that was asked for mu…
jdatcmd Oct 22, 2026
4209bed
test: phase3 gains its pytest twin, and a statement that fails inside…
jdatcmd Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ jobs:
- name: fetch the release tags, for the fixture-blob comparisons
run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*'

- name: Resolve the object-storage suite host names
# test_objstore_addressing.py, the twin of objstore_addressing.sh, reads
# through virtual-host-style S3 addressing (pgc-bucket.s3.local) against
# its own loopback fixture server, as the suites job's shells do. Without
# the names its tests decline UNAVAILABLE_ENDPOINT and the run ends 67.
run: echo "127.0.0.1 s3.local pgc-bucket.s3.local" | sudo tee -a /etc/hosts

- name: Add the PGDG repository and refresh the package lists
run: |
set -euo pipefail
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,13 @@ jobs:
- name: fetch the release tags, for the fixture-blob comparisons
run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*'

- name: Resolve the object-storage suite host names
# test_objstore_addressing.py, the twin of objstore_addressing.sh, reads
# through virtual-host-style S3 addressing (pgc-bucket.s3.local) against
# its own loopback fixture server, as the suites job's shells do. Without
# the names its tests decline UNAVAILABLE_ENDPOINT and the run ends 67.
run: echo "127.0.0.1 s3.local pgc-bucket.s3.local" | sudo tee -a /etc/hosts

- name: Add the PGDG repository and install PostgreSQL ${{ matrix.pg }} with its headers
run: |
set -euo pipefail
Expand Down
21 changes: 18 additions & 3 deletions objstore/columnar_objstore_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,15 @@ os_wait(PgColumnarObjHandle *h, uint32 sockEvents, int *quiet)
{
int rc;

/*
* WL_TIMEOUT, or the slice is not a slice: without it WaitLatchOrSocket
* ignores the timeout and waits for the socket or the latch alone, so a
* silent peer never counts a quiet slice. Under proc_exit (a terminated
* worker aborting its upload, #1727) interrupts are held and the latch
* alone never ends the wait either.
*/
rc = WaitLatchOrSocket(MyLatch,
WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | sockEvents,
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH | sockEvents,
h->fd, OS_WAIT_SLICE_MS, PG_WAIT_EXTENSION);
if (rc & WL_LATCH_SET)
ResetLatch(MyLatch);
Expand Down Expand Up @@ -2002,8 +2009,8 @@ os_flush_part(PgColumnarObjSink *s)
s->h->url, resp.status)));
/* The part's ETag is the server's word for its bytes; CompleteMultipart
* validates the list against it on a real S3, so echo it verbatim (already
* quoted by the server). The fixture ignores it and concatenates by part
* number, so both are satisfied. */
* quoted by the server). The fixture checks it the same way and assembles
* the listed parts, so both are satisfied. */
etag = resp.etag[0] ? resp.etag : "\"\"";
appendStringInfo(&s->completeXml,
"<Part><PartNumber>%d</PartNumber><ETag>%s</ETag></Part>",
Expand Down Expand Up @@ -2170,6 +2177,14 @@ objstore_sink_abort(PgColumnarObjSink *s)
char *encId = os_uriencode_query(s->uploadId);
char *query = psprintf("uploadId=%s", encId);

/*
* The abort can land mid-request (a cancel or a SIGTERM inside
* os_wait), with a part's body still on this connection: the
* DELETE would be read as more of that body, or its answer
* confused with the part's. A fresh connection makes it a request
* of its own (#1722).
*/
os_disconnect(s->h);
os_write_request(s->h, "DELETE", query, NULL, 0, &resp, NULL);
pfree(query);
pfree(encId);
Expand Down
67 changes: 67 additions & 0 deletions src/columnar_sink.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

#include <unistd.h>

#include "access/xact.h"
#include "lib/ilist.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/ipc.h"

#include "columnar_objstore.h"
#include "columnar_sink.h"
Expand All @@ -34,8 +37,62 @@ struct PqSink
/* remote (#394): NULL api => local sink */
const PgColumnarObjStoreApi *api;
PgColumnarObjSink *rsink;
dlist_node openNode; /* on sink_open_remote while rsink is live */
};

/*
* Remote sinks with an upload in progress (#1722). A FATAL exit (SIGTERM, as
* pexport_cleanup sends each parallel_export worker on cancel, or
* pg_terminate_backend) calls proc_exit without unwinding, so the writers'
* PG_CATCH never reaches PgColumnarSinkAbort and the multipart upload stays
* open on the store, billed until a lifecycle rule reaps it. A
* before_shmem_exit callback aborts whatever is still on this list. It is
* registered after InitPostgres's ShutdownPostgres, so it runs first, while
* the transaction and the module's handles are still intact. Transaction end
* empties the list: every writer finishes or aborts its sink inside its own
* statement, so an entry still listed then belongs to memory being released.
*/
static dlist_head sink_open_remote = DLIST_STATIC_INIT(sink_open_remote);
static bool sink_exit_registered = false;

static void
sink_abort_open_at_exit(int code, Datum arg)
{
dlist_mutable_iter it;

/*
* The module's abort never raises through PG_TRY, but under proc_exit an
* ERROR is promoted to FATAL, which no CATCH sees. A failed DELETE then
* ends this callback with one more FATAL line; shmem_exit has already
* popped it, so the remaining callbacks still run.
*/
dlist_foreach_modify(it, &sink_open_remote)
{
PqSink *snk = dlist_container(PqSink, openNode, it.cur);

dlist_delete(&snk->openNode);
snk->api->sink_abort(snk->rsink);
snk->rsink = NULL;
}
}

static void
sink_xact_callback(XactEvent event, void *arg)
{
switch (event)
{
case XACT_EVENT_COMMIT:
case XACT_EVENT_PARALLEL_COMMIT:
case XACT_EVENT_ABORT:
case XACT_EVENT_PARALLEL_ABORT:
case XACT_EVENT_PREPARE:
dlist_init(&sink_open_remote);
break;
default:
break;
}
}

PqSink *
PgColumnarSinkOpenLocal(const char *path)
{
Expand Down Expand Up @@ -83,6 +140,13 @@ PgColumnarSinkOpen(const char *path)
snk->finalPath = pstrdup(path);
snk->api = api;
snk->rsink = api->sink_create(path, NULL);
if (!sink_exit_registered)
{
before_shmem_exit(sink_abort_open_at_exit, (Datum) 0);
RegisterXactCallback(sink_xact_callback, NULL);
sink_exit_registered = true;
}
dlist_push_head(&sink_open_remote, &snk->openNode);
return snk;
}
return PgColumnarSinkOpenLocal(path);
Expand Down Expand Up @@ -140,6 +204,8 @@ PgColumnarSinkFinish(PqSink *snk)
if (snk->api != NULL)
{
snk->api->sink_finish(snk->rsink);
/* only now: a complete that raised leaves it listed for the abort */
dlist_delete(&snk->openNode);
snk->rsink = NULL;
return;
}
Expand Down Expand Up @@ -173,6 +239,7 @@ PgColumnarSinkAbort(PqSink *snk)
{
if (snk->rsink != NULL)
{
dlist_delete(&snk->openNode);
snk->api->sink_abort(snk->rsink);
snk->rsink = NULL;
}
Expand Down
Loading
Loading