diff -Nru ubuntu-app-launch-0.9+17.04.20170202.2/debian/changelog ubuntu-app-launch-0.9+17.04.20170206/debian/changelog --- ubuntu-app-launch-0.9+17.04.20170202.2/debian/changelog 2017-02-07 17:31:17.000000000 +0000 +++ ubuntu-app-launch-0.9+17.04.20170206/debian/changelog 2017-02-07 17:31:18.000000000 +0000 @@ -1,3 +1,9 @@ +ubuntu-app-launch (0.9+17.04.20170206-0ubuntu1) zesty; urgency=medium + + * Reset failed units so they can be tried again (LP: #1655754) + + -- Ted Gould Mon, 06 Feb 2017 16:00:40 +0000 + ubuntu-app-launch (0.9+17.04.20170202.2-0ubuntu1) zesty; urgency=medium * SystemD backend added diff -Nru ubuntu-app-launch-0.9+17.04.20170202.2/libubuntu-app-launch/jobs-systemd.cpp ubuntu-app-launch-0.9+17.04.20170206/libubuntu-app-launch/jobs-systemd.cpp --- ubuntu-app-launch-0.9+17.04.20170202.2/libubuntu-app-launch/jobs-systemd.cpp 2017-02-02 15:09:24.000000000 +0000 +++ ubuntu-app-launch-0.9+17.04.20170206/libubuntu-app-launch/jobs-systemd.cpp 2017-02-06 16:00:19.000000000 +0000 @@ -1342,6 +1342,9 @@ } g_variant_dict_clear(&dict); + /* Reset the failure bit on the unit */ + manager->resetUnit(unitinfo); + /* Oh, we might want to do something now */ auto reason{Registry::FailureType::CRASH}; if (g_strcmp0(value, "exit-code") == 0) @@ -1368,6 +1371,50 @@ return sig_appFailed; } +/** Requests that systemd reset a unit that has been marked as + failed so that we can continue to work with it. This includes + starting it anew, which can fail if it is left in the failed + state. */ +void SystemD::resetUnit(const UnitInfo& info) const +{ + auto registry = registry_.lock(); + auto unitname = unitName(info); + auto bus = userbus_; + auto cancel = registry->impl->thread.getCancellable(); + + registry->impl->thread.executeOnThread([bus, unitname, cancel] { + g_dbus_connection_call(bus.get(), /* user bus */ + SYSTEMD_DBUS_ADDRESS, /* bus name */ + SYSTEMD_DBUS_PATH_MANAGER, /* path */ + SYSTEMD_DBUS_IFACE_MANAGER, /* interface */ + "ResetFailedUnit", /* method */ + g_variant_new("(s)", /* params */ + unitname.c_str()), /* param: specify unit */ + nullptr, /* ret type */ + G_DBUS_CALL_FLAGS_NONE, /* flags */ + -1, /* timeout */ + cancel.get(), /* cancellable */ + [](GObject* obj, GAsyncResult* res, gpointer user_data) { + GError* error{nullptr}; + GVariant* callt = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error); + + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning("Unable to reset failed unit: %s", error->message); + } + g_error_free(error); + return; + } + + g_clear_pointer(&callt, g_variant_unref); + g_debug("Reset Failed Unit"); + }, + nullptr); + }); +} + } // namespace manager } // namespace jobs } // namespace app_launch diff -Nru ubuntu-app-launch-0.9+17.04.20170202.2/libubuntu-app-launch/jobs-systemd.h ubuntu-app-launch-0.9+17.04.20170206/libubuntu-app-launch/jobs-systemd.h --- ubuntu-app-launch-0.9+17.04.20170202.2/libubuntu-app-launch/jobs-systemd.h 2017-02-02 15:09:05.000000000 +0000 +++ ubuntu-app-launch-0.9+17.04.20170206/libubuntu-app-launch/jobs-systemd.h 2017-02-06 16:00:19.000000000 +0000 @@ -128,6 +128,8 @@ static std::vector parseExec(std::list>& env); static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data); + + void resetUnit(const UnitInfo& info) const; }; } // namespace manager diff -Nru ubuntu-app-launch-0.9+17.04.20170202.2/tests/jobs-systemd.cpp ubuntu-app-launch-0.9+17.04.20170206/tests/jobs-systemd.cpp --- ubuntu-app-launch-0.9+17.04.20170202.2/tests/jobs-systemd.cpp 2017-02-02 15:09:05.000000000 +0000 +++ ubuntu-app-launch-0.9+17.04.20170206/tests/jobs-systemd.cpp 2017-02-06 16:00:19.000000000 +0000 @@ -360,3 +360,27 @@ EXPECT_EQ(multipleAppID(), removeunit.get_future().get()); } + +TEST_F(JobsSystemd, UnitFailure) +{ + auto manager = std::make_shared(registry); + registry->impl->jobs = manager; + + ubuntu::app_launch::AppID failedappid; + manager->appFailed().connect([&](const std::shared_ptr &app, + const std::shared_ptr &inst, + ubuntu::app_launch::Registry::FailureType type) { failedappid = app->appId(); }); + + systemd->managerEmitFailed({defaultJobName(), std::string{multipleAppID()}, "1234567890", 1, {}}); + + EXPECT_EVENTUALLY_EQ(multipleAppID(), failedappid); + + std::list resets; + EXPECT_EVENTUALLY_FUNC_LT(0u, std::function([&]() { + resets = systemd->resetCalls(); + return resets.size(); + })); + + EXPECT_EQ(SystemdMock::instanceName({defaultJobName(), std::string{multipleAppID()}, "1234567890", 1, {}}), + *resets.begin()); +} diff -Nru ubuntu-app-launch-0.9+17.04.20170202.2/tests/systemd-mock.h ubuntu-app-launch-0.9+17.04.20170206/tests/systemd-mock.h --- ubuntu-app-launch-0.9+17.04.20170202.2/tests/systemd-mock.h 2017-02-02 15:09:05.000000000 +0000 +++ ubuntu-app-launch-0.9+17.04.20170206/tests/systemd-mock.h 2017-02-06 16:00:19.000000000 +0000 @@ -140,6 +140,11 @@ "ret = '/'", &error); throwError(error); + dbus_test_dbus_mock_object_add_method(mock, managerobj, "ResetFailedUnit", G_VARIANT_TYPE_STRING, + nullptr, /* ret type */ + "", &error); + throwError(error); + for (auto& instance : instances) { auto obj = dbus_test_dbus_mock_get_object(mock, instancePath(instance).c_str(), @@ -148,6 +153,9 @@ dbus_test_dbus_mock_object_add_property(mock, obj, "MainPID", G_VARIANT_TYPE_UINT32, g_variant_new_uint32(instance.primaryPid), &error); throwError(error); + dbus_test_dbus_mock_object_add_property(mock, obj, "Result", G_VARIANT_TYPE_STRING, + g_variant_new_string("success"), &error); + throwError(error); /* Control Group */ auto dir = g_build_filename(controlGroupPath.c_str(), instancePath(instance).c_str(), nullptr); @@ -432,6 +440,46 @@ return retval; } + std::list resetCalls() + { + guint len = 0; + GError* error = nullptr; + + auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ + managerobj, /* manager */ + "ResetFailedUnit", /* function */ + &len, /* number */ + &error /* error */ + ); + + if (error != nullptr) + { + g_warning("Unable to get 'ResetFailedUnit' calls from systemd mock: %s", error->message); + g_error_free(error); + throw std::runtime_error{"Mock disfunctional"}; + } + + std::list retval; + + for (unsigned int i = 0; i < len; i++) + { + auto& call = calls[i]; + gchar* name = nullptr; + + g_variant_get(call.params, "(&s)", &name); + + if (name == nullptr) + { + g_warning("Invalid 'name' on 'ResetFailedUnit' call"); + continue; + } + + retval.emplace_back(name); + } + + return retval; + } + void managerClear() { GError* error = nullptr; @@ -477,5 +525,30 @@ g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } + } + + void managerEmitFailed(const Instance& inst) + { + auto instobj = + std::find_if(insts.begin(), insts.end(), [inst](const std::pair& item) { + return item.first.job == inst.job && item.first.appid == inst.appid && + item.first.instanceid == inst.instanceid; + }); + + if (instobj == insts.end()) + { + throw std::runtime_error{"Unable to find instance"}; + } + + GError* error = nullptr; + dbus_test_dbus_mock_object_update_property(mock, instobj->second, "Result", g_variant_new_string("fail"), + &error); + + if (error != nullptr) + { + g_warning("Unable to set result to 'fail': %s", error->message); + g_error_free(error); + throw std::runtime_error{"Mock disfunctional"}; + } } };