Keyple Service C++ Library - 3.3.5
Component of the Keyple C++ middleware
ExecutorService.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ *
3 * *
4 * See the NOTICE file(s) distributed with this work for additional *
5 * information regarding copyright ownership. *
6 * *
7 * This program and the accompanying materials are made available under the *
8 * terms of the Eclipse Public License 2.0 which is available at *
9 * http://www.eclipse.org/legal/epl-2.0 *
10 * *
11 * SPDX-License-Identifier: EPL-2.0 *
12 ******************************************************************************/
13
14#include "keyple/core/service/cpp/ExecutorService.hpp"
15
16#include <condition_variable>
17#include <memory>
18#include <mutex>
19#include <thread>
20
21#include "keyple/core/service/AbstractObservableStateAdapter.hpp"
22#include "keyple/core/util/cpp/Thread.hpp"
23
24namespace keyple {
25namespace core {
26namespace service {
27namespace cpp {
28
29using keyple::core::service::AbstractObservableStateAdapter;
30using keyple::core::util::cpp::Thread;
31
32ExecutorService::ExecutorService()
33: mRunning(false)
34, mShutdown(false)
35, mTerminated(false)
36{
37}
38
39ExecutorService::~ExecutorService()
40{
41 shutdown();
42}
43
44void
45ExecutorService::run()
46{
47 while (true) {
48 std::unique_lock<std::mutex> lock(mMutex);
49
50 /* Wait until there's a job or the service is shutting down */
51 mCondition.wait(lock, [this] { return !mPool.empty() || !mRunning; });
52
53 /* Check if we should terminate */
54 if (!mRunning && mPool.empty()) {
55 break;
56 }
57
58 /* Get the job and remove it from the pool */
59 std::shared_ptr<Job> job = mPool.front();
60 mPool.erase(mPool.begin());
61
62 /*
63 * Unlock the mutex before running the job
64 * This allows other threads to submit new jobs while one is being
65 * processed
66 */
67 lock.unlock();
68
69 if (!job->isCancelled()) {
70 /*
71 * A failing job must not bring down the worker thread, and even
72 * less the process: a Java ThreadPoolExecutor captures the
73 * exception of a task in its Future and keeps the pool alive.
74 */
75 try {
76 job->run();
77
78 } catch (const std::exception& e) {
79 mLogger->error("Job [%] failed: %\n", job->getName(), e.what());
80
81 } catch (...) {
82 mLogger->error(
83 "Job [%] failed with an unknown exception\n",
84 job->getName());
85 }
86 }
87 }
88
89 mTerminated = true;
90}
91
92void
93ExecutorService::execute(std::shared_ptr<Job> job)
94{
95 {
96 std::lock_guard<std::mutex> lock(mMutex);
97 /*
98 * Once shut down, reject the job as a Java ThreadPoolExecutor does.
99 * Accepting it would restart a worker thread that no shutdown will
100 * ever join, letting the job outlive the object that submitted it.
101 */
102 if (mShutdown) {
103 return;
104 }
105 if (!mThread) {
106 mRunning = true;
107 mThread = std::unique_ptr<std::thread>(
108 new std::thread(&ExecutorService::run, this));
109 }
110 mPool.push_back(job);
111 }
112 mCondition.notify_one();
113}
114
115std::shared_ptr<Job>
116ExecutorService::submit(std::shared_ptr<Job> job)
117{
118 /*
119 * Return the job we were given directly: the worker thread may already
120 * have dequeued (and even completed) it by the time we could re-lock
121 * mMutex, so reading it back via mPool.back() is a data race that can
122 * return an empty-vector access or the wrong job entirely.
123 */
124 execute(job);
125 return job;
126}
127
128void
129ExecutorService::shutdown()
130{
131 {
132 std::lock_guard<std::mutex> lock(mMutex);
133 mShutdown = true;
134 if (!mThread) {
135 return;
136 }
137 mRunning = false;
138 }
139
140 mCondition.notify_one();
141
142 if (mThread->joinable()) {
143 mThread->join();
144 }
145
146 mThread.reset();
147 mTerminated = true;
148}
149
150} /* namespace cpp */
151} /* namespace service */
152} /* namespace core */
153} /* namespace keyple */