1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtSystems module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qserviceoperations_p.h"
35#include "qservicerequest_p.h"
36#include "qservicereply.h"
37#include "qservicedebuglog_p.h"
38#include "qservicereply_p.h"
39
40Q_GLOBAL_STATIC(QServiceOperations, q_service_operations_object)
41
42//// Implementation notes: The background thread is implemented as a facade
43//// behind which the actual worker object is hidden. The facade - an instance
44//// of QServiceOperations - lives in (has QObject affinity to) the *foreground*
45//// calling thread.
46////
47//// The worker object, an instance of QServiceOperationProcessor, is created
48//// on the stack inside the run() method of the QThread, thus ensuring it does
49//// not need to be cleaned up by some deleter. Since it is created in the
50//// run() it lives in the *background* thread, and connections to it from the
51//// facade (QServiceOperations) are thus queued connections.
52////
53//// Signal-slot connections are thus two-level indirection, but what this means
54//// is that no locking is needed in QServiceOperationProcessor since it can
55//// only be accessed within its own thread, by calls that are serialised into
56//// its slots by the signal-slot event queue.
57
58QServiceOperations::QServiceOperations(QObject *parent)
59 : QThread(parent)
60 , m_engageCount(0)
61{
62 // This call must be executed before the first signal-slot connection
63 // which uses a QServiceRequest. Since this constructor will need to fire
64 // before we have an instance of this class to connect anything to
65 // we should be OK here.
66 qRegisterMetaType<QServiceRequest>(typeName: "QServiceRequest");
67 qRegisterMetaType<QServiceManager::Error>(typeName: "QServiceManager::Error");
68
69 // created in the constructor otherwise we can signals/slots
70 QServiceOperationProcessor *processor = new QServiceOperationProcessor();
71 processor->moveToThread(thread: this);
72
73 // This call creates a queued connection
74 connect(sender: this, SIGNAL(newRequest(QServiceRequest)),
75 receiver: processor, SLOT(handleRequest(QServiceRequest)), Qt::QueuedConnection);
76 connect(sender: this, SIGNAL(destroyed()),
77 receiver: processor, SLOT(deleteLater()));
78
79 qServiceLog() << "event" << "new"
80 << "class" << "QServiceOperations";
81}
82
83QServiceOperations::~QServiceOperations()
84{
85 qServiceLog() << "event" << "delete"
86 << "class" << "QServiceOperations";
87}
88
89/*
90 Return the per-process instance of the thread facade object
91*/
92QServiceOperations *QServiceOperations::instance()
93{
94 qServiceLog() << "event" << "instance access"
95 << "class" << "QServiceOperations"
96 << "clients" << q_service_operations_object()->clientCount();
97
98 return q_service_operations_object();
99}
100
101/*
102 Calling code engages the thread facade, marking themselves as users of it.
103 The first caller will cause the thread to start. Client code should call
104 engage early during initialisation as the once-off thread startup cost could
105 cause latency for time critical operations if its done on-demand.
106*/
107void QServiceOperations::engage()
108{
109 if (m_engageCount.testAndSetAcquire(expectedValue: 0, newValue: 1)) {
110 start();
111 } else {
112 m_engageCount.ref();
113 }
114 qServiceLog() << "event" << "engage"
115 << "class" << "QServiceOperations"
116 << "clients" << q_service_operations_object()->clientCount();
117}
118
119/*
120 Client code disengages from the thread, releasing themselves from use of it.
121 The last client to do so will cause the thread to quit, exiting the event
122 loop, and running the thread shutdown process. Normally this should be a
123 100ms or so, but if for some reason there are problems it could take longer.
124 This might be a bug, or exceptional condition, or some long running request
125 that just happens to be in flight at the time. The current implementation
126 will try 3 times to quit the thread (with a 500ms window each time) for a
127 total worst-case of 1500ms; before calling terminate on the thread. After
128 that the termination will still take a short period of time before its
129 safe to clean up the thread object.
130*/
131void QServiceOperations::disengage()
132{
133 qServiceLog() << "event" << "disengage"
134 << "class" << "QServiceOperations"
135 << "clients" << q_service_operations_object()->clientCount();
136
137 if (!m_engageCount.deref()) {
138 qServiceLog() << "event" << "shutdown"
139 << "class" << "QServiceOperations";
140 quit();
141 int triesLeft = 3;
142 bool exitedCleanly = false;
143 while (triesLeft) {
144 exitedCleanly = wait(time: 500);
145 if (exitedCleanly)
146 break;
147 qWarning() << "Waiting for QServiceOperations background thread to exit...";
148 triesLeft--;
149 }
150 if (!exitedCleanly) {
151 qWarning() << "...forcing termination of QServiceOperations thread!";
152 terminate();
153 wait();
154 }
155 }
156}
157
158/*
159 Make a new request. This slot will be called synchronously from the client
160 code, since the client and the facade are in the same thread. However this
161 slot then forwards the request on via a queued connection to the internal
162 operations object. The queue automatically gives thread safety to the
163 processor meaning no locking is required.
164*/
165void QServiceOperations::initiateRequest(const QServiceRequest &req)
166{
167 qServiceLog() << "event" << "initiate"
168 << "class" << "QServiceOperations"
169 << "iface" << req.descriptor().interfaceName();
170
171 emit newRequest(req);
172}
173
174/*
175 This function will be called when start() is called in the engage() code above.
176 All the code in run() will execute in the background thread.
177*/
178void QServiceOperations::run()
179{
180 qServiceLog() << "event" << "run"
181 << "class" << "QServiceOperations"
182 << "priority" << (qint32)priority();
183
184 // spin the per-thread event loop
185 exec();
186}
187
188
189////////////////////////////////////////////////////
190////
191//// QServiceOperationProcessor implementation
192
193/*
194 Constructor - stateless object at present.
195*/
196QServiceOperationProcessor::QServiceOperationProcessor(QObject *parent)
197 : QObject(parent), inRequest(false)
198{
199 qServiceLog() << "event" << "new"
200 << "class" << "QServiceOperationProc";
201}
202
203/*
204 Destructor - no resources. Should destruct when the main event
205 loop of the facade exits.
206*/
207QServiceOperationProcessor::~QServiceOperationProcessor()
208{
209 qServiceLog() << "event" << "delete"
210 << "class" << "QServiceOperationProc";
211}
212
213/*
214 Do the actual work. Note that all calls to this function are serialised by
215 the queued signal-slot connection from the facade. But since SFW could
216 spin the even loop we need to lock loadInterface so it's only called once
217*/
218void QServiceOperationProcessor::handleRequest(const QServiceRequest &inrequest)
219{
220 pendingList.append(t: inrequest);
221
222 if (inRequest) {
223 qServiceLog() << "event" << "already handle req"
224 << "class" << "QServiceOperationProc"
225 << "iface" << inrequest.descriptor().interfaceName();
226 return;
227 }
228
229 inRequest = true;
230
231 QServiceRequest req;
232
233 while (!pendingList.isEmpty()) {
234 req = pendingList.takeFirst();
235
236 qServiceLog() << "event" << "handle req start"
237 << "class" << "QServiceOperationProc"
238 << "iface" << inrequest.descriptor().interfaceName();
239
240 QServiceInterfaceDescriptor descriptor = req.descriptor();
241 QServiceReply *reply = req.reply();
242
243 QServiceManager mgr(req.scope());
244
245 QMetaObject::invokeMethod(obj: reply, member: "start", type: Qt::QueuedConnection);
246
247 if (req.requestType() == QServiceRequest::DefaultInterfaceRequest) {
248 // OK, this was a request based on the interface name rather than a
249 // fully specified descriptor
250 qDebug() << "Asking for the default interface for" << mgr.interfaceDefault(interfaceName: req.interfaceName());
251 descriptor = mgr.interfaceDefault(interfaceName: req.interfaceName());
252 } else {
253 qDebug() << "NOT asking for default";
254 }
255
256 if (!descriptor.isValid()) {
257 qDebug() << "Failed to fetch default";
258 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection, Q_ARG(QServiceManager::Error, QServiceManager::InvalidServiceInterfaceDescriptor));
259 QMetaObject::invokeMethod(obj: reply, member: "finish", type: Qt::QueuedConnection);
260 continue;
261 }
262
263 QObject *obj = 0;
264 int serviceType = descriptor.attribute(which: QServiceInterfaceDescriptor::ServiceType).toInt();
265 const QString location = descriptor.attribute(which: QServiceInterfaceDescriptor::Location).toString();
266
267 if (serviceType == QService::InterProcess) {
268 obj = mgr.loadInterProcessService(descriptor, location);
269 } else {
270
271 const QString serviceFilePath = mgr.resolveLibraryPath(libNameOrPath: location);
272 if (serviceFilePath.isEmpty()) {
273 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection, Q_ARG(QServiceManager::Error, QServiceManager::InvalidServiceLocation));
274 QMetaObject::invokeMethod(obj: reply, member: "finish", type: Qt::QueuedConnection);
275 continue;
276 }
277
278 obj = mgr.loadInProcessService(descriptor, serviceFilePath);
279 if (!obj) {
280 QMetaObject::invokeMethod(obj: reply, member: "setError", type: Qt::QueuedConnection, Q_ARG(QServiceManager::Error, QServiceManager::PluginLoadingFailed));
281 QMetaObject::invokeMethod(obj: reply, member: "finish", type: Qt::QueuedConnection);
282 continue;
283 }
284 }
285
286 // loadInterface() can return null
287 if (obj) {
288 obj->moveToThread(thread: reply->thread());
289 }
290
291 QMetaObject::invokeMethod(obj: reply, member: "setProxyObject", type: Qt::QueuedConnection, Q_ARG(QObject*, obj));
292 QMetaObject::invokeMethod(obj: reply, member: "finish", type: Qt::QueuedConnection);
293
294 qServiceLog() << "event" << "handle req done"
295 << "class" << "QServiceOperationProc"
296 << "iface" << inrequest.descriptor().interfaceName();
297 }
298
299 inRequest = false;
300}
301

source code of qtsystems/src/serviceframework/qserviceoperations.cpp