Fix dangling reference in scanner event proxy and missing transaction in share artwork
- main.cpp: proxyScannerEventsToApplication() captured its local `postAll` lambda by reference ([&]) inside connections registered on the scanner service's long-lived signals. Those connections outlive the function call, so every scan event since startup was invoking a dangling reference to an already-popped stack frame - undefined behavior, deep in Wt's own signal dispatch, from the very first scan onward. This lines up with what we're seeing in production: sporadic SIGSEGV crashes (exit code 139) inside Wt::Signals::Impl::SignalLinkBase::isConnected(), confirmed via host dmesg, roughly every 1-2 hours of uptime. Fixed by capturing `postAll` by value (it's a stateless lambda, so this is free); `server` remains a valid reference since it lives for the whole program. - PublicShareResource.cpp: the /artwork branch called IArtworkService::getImage(), which lazily resolves artwork through the DB, outside of any transaction (the read transaction that populated `items` had already closed). Confirmed in production logs: "Uncaught exception from handleRequest (aborting request): Operation requires an active transaction". Wrapped it in a read transaction, matching the pattern used elsewhere in this file.
This commit is contained in:
@@ -186,6 +186,11 @@ namespace lms
|
|||||||
if (parts.size() != 3) { response.setStatus(404); return; }
|
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]; }) };
|
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; }
|
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<artwork::IArtworkService>::get() };
|
auto artworkService{ core::Service<artwork::IArtworkService>::get() };
|
||||||
auto image{ item->artworkId.isValid() ? artworkService->getImage(item->artworkId, 256) : nullptr };
|
auto image{ item->artworkId.isValid() ? artworkService->getImage(item->artworkId, 256) : nullptr };
|
||||||
if (!image) image = artworkService->getDefaultReleaseArtwork();
|
if (!image) image = artworkService->getDefaultReleaseArtwork();
|
||||||
|
|||||||
+11
-5
@@ -241,6 +241,12 @@ namespace lms
|
|||||||
|
|
||||||
void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server)
|
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<void()> cb) {
|
auto postAll{ [](Wt::WServer& server, std::function<void()> cb) {
|
||||||
server.postAll([cb = std::move(cb)] {
|
server.postAll([cb = std::move(cb)] {
|
||||||
// may be nullptr, see https://redmine.webtoolkit.eu/issues/8202
|
// 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, [] {
|
postAll(server, [] {
|
||||||
LmsApp->getScannerEvents().scanAborted.emit();
|
LmsApp->getScannerEvents().scanAborted.emit();
|
||||||
LmsApp->triggerUpdate();
|
LmsApp->triggerUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
scanner.getEvents().scanStarted.connect([&] {
|
scanner.getEvents().scanStarted.connect([postAll, &server] {
|
||||||
postAll(server, [] {
|
postAll(server, [] {
|
||||||
LmsApp->getScannerEvents().scanStarted.emit();
|
LmsApp->getScannerEvents().scanStarted.emit();
|
||||||
LmsApp->triggerUpdate();
|
LmsApp->triggerUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) {
|
scanner.getEvents().scanComplete.connect([postAll, &server](const scanner::ScanStats& stats) {
|
||||||
postAll(server, [=] {
|
postAll(server, [=] {
|
||||||
LmsApp->getScannerEvents().scanComplete.emit(stats);
|
LmsApp->getScannerEvents().scanComplete.emit(stats);
|
||||||
LmsApp->triggerUpdate();
|
LmsApp->triggerUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) {
|
scanner.getEvents().scanInProgress.connect([postAll, &server](const scanner::ScanStepStats& stats) {
|
||||||
postAll(server, [=] {
|
postAll(server, [=] {
|
||||||
LmsApp->getScannerEvents().scanInProgress.emit(stats);
|
LmsApp->getScannerEvents().scanInProgress.emit(stats);
|
||||||
LmsApp->triggerUpdate();
|
LmsApp->triggerUpdate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) {
|
scanner.getEvents().scanScheduled.connect([postAll, &server](const Wt::WDateTime dateTime) {
|
||||||
postAll(server, [=] {
|
postAll(server, [=] {
|
||||||
LmsApp->getScannerEvents().scanScheduled.emit(dateTime);
|
LmsApp->getScannerEvents().scanScheduled.emit(dateTime);
|
||||||
LmsApp->triggerUpdate();
|
LmsApp->triggerUpdate();
|
||||||
|
|||||||
Reference in New Issue
Block a user