diff --git a/src/lms/PublicShareResource.cpp b/src/lms/PublicShareResource.cpp index 8084991e..337506fa 100644 --- a/src/lms/PublicShareResource.cpp +++ b/src/lms/PublicShareResource.cpp @@ -186,6 +186,11 @@ namespace lms if (parts.size() != 3) { response.setStatus(404); return; } const auto item{ std::find_if(items.begin(), items.end(), [&](const auto& value) { return value.id == parts[2]; }) }; if (item == items.end()) { response.setStatus(404); return; } + // IArtworkService::getImage() resolves the artwork through the DB (Wt::Dbo + // lazy-loads on access), so this needs an active transaction on this thread's + // session, same as the block above that populated `items`. Without it, this + // throws "Operation requires an active transaction" (seen in production logs). + auto transaction = _db.getTLSSession().createReadTransaction(); auto artworkService{ core::Service::get() }; auto image{ item->artworkId.isValid() ? artworkService->getImage(item->artworkId, 256) : nullptr }; if (!image) image = artworkService->getDefaultReleaseArtwork(); diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 67614d60..c6021ada 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -241,6 +241,12 @@ namespace lms void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server) { + // NOTE: these connections are stored on `scanner`, which outlives this function call + // (it's a core::Service owned by main()'s scope), so nothing here may capture a + // reference to a local of this function. `postAll` used to be captured via `[&]`, + // which left every connected slot holding a dangling reference to this function's + // (long-since-popped) stack frame for the remainder of the program's lifetime. + // Capture it by value instead; it's a stateless lambda, so this is free. auto postAll{ [](Wt::WServer& server, std::function cb) { server.postAll([cb = std::move(cb)] { // may be nullptr, see https://redmine.webtoolkit.eu/issues/8202 @@ -249,35 +255,35 @@ namespace lms }); } }; - scanner.getEvents().scanAborted.connect([&] { + scanner.getEvents().scanAborted.connect([postAll, &server] { postAll(server, [] { LmsApp->getScannerEvents().scanAborted.emit(); LmsApp->triggerUpdate(); }); }); - scanner.getEvents().scanStarted.connect([&] { + scanner.getEvents().scanStarted.connect([postAll, &server] { postAll(server, [] { LmsApp->getScannerEvents().scanStarted.emit(); LmsApp->triggerUpdate(); }); }); - scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) { + scanner.getEvents().scanComplete.connect([postAll, &server](const scanner::ScanStats& stats) { postAll(server, [=] { LmsApp->getScannerEvents().scanComplete.emit(stats); LmsApp->triggerUpdate(); }); }); - scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) { + scanner.getEvents().scanInProgress.connect([postAll, &server](const scanner::ScanStepStats& stats) { postAll(server, [=] { LmsApp->getScannerEvents().scanInProgress.emit(stats); LmsApp->triggerUpdate(); }); }); - scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) { + scanner.getEvents().scanScheduled.connect([postAll, &server](const Wt::WDateTime dateTime) { postAll(server, [=] { LmsApp->getScannerEvents().scanScheduled.emit(dateTime); LmsApp->triggerUpdate();