32cd2100d9dd11140705c848850e86c4f79191c4 max Tue Sep 1 07:02:04 2026 -0700 Reusable "view snapshot" sessions for durable, minimal Share-a-link links "Share a link" could only share the whole cart: it saved a session holding every track, setting and position, which bloated the central db and leaked the sharer's unrelated tracks to whoever opened the link. Anonymous share sessions (under the reserved user "l") were also never reaped, so they accumulated forever, and their 8-char names were generated client-side with no uniqueness check, so two shares could collide and silently overwrite. Adds a lightweight "snapshot session" facility (lib/snapshotSession.c): a snapshot stores only the handful of cart variables a feature declares (a registered snapshotType, e.g. "blat" -> {db, blatLastBigBed}), moves only those variables' trash files into durable sessionData storage, and is saved under a "__"-prefixed name. The "__" marks it machine-made: hidden from the My Sessions list by default and eligible for reaping. For the anonymous "l" owner the durable files fan out over two extra hash levels so one directory never fills with millions of entries. Every anonymous link now shares one server-side name generator (snapshotNewName): a unique (db-checked), crypto-strong, "__"-prefixed token, so tokens never collide. The hgSession doSaveSessionJson endpoint gained hgS_snapshotType (save a minimal snapshot rather than the whole cart) and hgS_doAnonName (reserve a unique anonymous name without saving, so the top-right dialog can preview the exact link before it is created). Wired three callers to the facility: - hgc htcBlatAlign "Share a link": a minimal "blat" snapshot instead of a full-cart anonymous session. - hgBlat results "Share a link": creates a "blat" snapshot on click and reveals its ?u=&s= reopen link (rebuilt from the durable bigPsl by the existing doShareReopen), replacing the trash-only reveal. - top-right "Share a link": anonymous links use the reserved server name; logged-in named shares are unchanged. snapshotReaper (hg/utils) garbage-collects abandoned anonymous snapshots: it deletes user "l" "__" rows whose lastUse is older than the TTL (hg.conf snapshot.ttlDays, default ~4 years) and removes their durable files. lastUse is bumped on every open by the existing session load, so a link stays alive as long as it is used. Meant to run from the trash-cleaner cron. Also folds in the recent Share-dialog work in these files: the auto share name is a short "_" prefix instead of "share_", the dialog previews the link and creates it only when the button is clicked (no orphan session just from opening the dialog), an optional name field with an overwrite warning, and the anonymous save path forces the reap-eligible "__" name. refs #38197 diff --git src/hg/utils/snapshotReaper/snapshotReaper.c src/hg/utils/snapshotReaper/snapshotReaper.c new file mode 100644 index 00000000000..86fb0716222 --- /dev/null +++ src/hg/utils/snapshotReaper/snapshotReaper.c @@ -0,0 +1,60 @@ +/* snapshotReaper - garbage-collect abandoned anonymous shareable-view snapshots. + * + * Shareable "view snapshot" sessions (see hg/lib/snapshotSession.c) are lightweight named sessions + * created to back durable share links (e.g. a BLAT alignment). Anonymous ones live under the + * reserved user "l" with a "__"-prefixed name. This reaper deletes those whose lastUse is older + * than the TTL, along with their durable sessionData files, giving share links a "durable while + * used, reaped when abandoned" lifetime. It never touches real (non-"__") or non-anonymous + * sessions. Intended to run from the same cron as the trash cleaner. + * + * Copyright (C) 2026 The Regents of the University of California + * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ + +#include "common.h" +#include "options.h" +#include "jksql.h" +#include "hdb.h" +#include "hgConfig.h" +#include "snapshotSession.h" + +void usage() +/* Explain usage and exit. */ +{ +errAbort( + "snapshotReaper - delete abandoned anonymous view-snapshot sessions and their durable files.\n" + "usage:\n" + " snapshotReaper [options]\n" + "options:\n" + " -ttlDays=N Reap anonymous snapshots not opened within N days.\n" + " Default: the hg.conf setting snapshot.ttlDays, else %d (~4 years).\n" + " -dryRun Report how many would be reaped without deleting anything.\n", + snapshotDefaultTtlDays); +} + +static struct optionSpec options[] = { + {"ttlDays", OPTION_INT}, + {"dryRun", OPTION_BOOLEAN}, + {NULL, 0}, +}; + +int main(int argc, char *argv[]) +{ +optionInit(&argc, argv, options); +if (argc != 1) + usage(); +int ttlDays = snapshotDefaultTtlDays; +char *cfgTtl = cfgOption("snapshot.ttlDays"); +if (isNotEmpty(cfgTtl)) + ttlDays = atoi(cfgTtl); +ttlDays = optionInt("ttlDays", ttlDays); // command line wins over hg.conf +if (ttlDays < 1) + errAbort("snapshotReaper: ttlDays must be at least 1 (got %d)", ttlDays); +boolean dryRun = optionExists("dryRun"); + +struct sqlConnection *conn = hConnectCentral(); +int n = snapshotReapAnon(conn, ttlDays, dryRun); +hDisconnectCentral(&conn); +verbose(1, "%s %d abandoned anonymous snapshot(s) older than %d days.\n", + dryRun ? "Would reap" : "Reaped", n, ttlDays); +return 0; +}