VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/AutoCaller.cpp@ 86714

Last change on this file since 86714 was 85929, checked in by vboxsync, 4 years ago

Main: bugref:9224: Main+VBoxManageDisk+doc part

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.2 KB
Line 
1/* $Id: AutoCaller.cpp 85929 2020-08-28 14:40:55Z vboxsync $ */
2/** @file
3 * VirtualBox object state implementation
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN
19#include <iprt/semaphore.h>
20
21#include "VirtualBoxBase.h"
22#include "AutoCaller.h"
23#include "LoggingNew.h"
24
25
26////////////////////////////////////////////////////////////////////////////////
27//
28// ObjectState methods
29//
30////////////////////////////////////////////////////////////////////////////////
31
32
33ObjectState::ObjectState() : mStateLock(LOCKCLASS_OBJECTSTATE)
34{
35 AssertFailed();
36}
37
38ObjectState::ObjectState(VirtualBoxBase *aObj) :
39 mObj(aObj), mStateLock(LOCKCLASS_OBJECTSTATE)
40{
41 Assert(mObj);
42 mState = NotReady;
43 mStateChangeThread = NIL_RTTHREAD;
44 mCallers = 0;
45 mFailedRC = S_OK;
46 mpFailedEI = NULL;
47 mZeroCallersSem = NIL_RTSEMEVENT;
48 mInitUninitSem = NIL_RTSEMEVENTMULTI;
49 mInitUninitWaiters = 0;
50}
51
52ObjectState::~ObjectState()
53{
54 Assert(mInitUninitWaiters == 0);
55 Assert(mInitUninitSem == NIL_RTSEMEVENTMULTI);
56 if (mZeroCallersSem != NIL_RTSEMEVENT)
57 RTSemEventDestroy(mZeroCallersSem);
58 mCallers = 0;
59 mStateChangeThread = NIL_RTTHREAD;
60 mState = NotReady;
61 mFailedRC = S_OK;
62 if (mpFailedEI)
63 {
64 delete mpFailedEI;
65 mpFailedEI = NULL;
66 }
67 mObj = NULL;
68}
69
70ObjectState::State ObjectState::getState()
71{
72 AutoReadLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
73 return mState;
74}
75
76/**
77 * Increments the number of calls to this object by one.
78 *
79 * After this method succeeds, it is guaranteed that the object will remain
80 * in the Ready (or in the Limited) state at least until #releaseCaller() is
81 * called.
82 *
83 * This method is intended to mark the beginning of sections of code within
84 * methods of COM objects that depend on the readiness (Ready) state. The
85 * Ready state is a primary "ready to serve" state. Usually all code that
86 * works with component's data depends on it. On practice, this means that
87 * almost every public method, setter or getter of the object should add
88 * itself as an object's caller at the very beginning, to protect from an
89 * unexpected uninitialization that may happen on a different thread.
90 *
91 * Besides the Ready state denoting that the object is fully functional,
92 * there is a special Limited state. The Limited state means that the object
93 * is still functional, but its functionality is limited to some degree, so
94 * not all operations are possible. The @a aLimited argument to this method
95 * determines whether the caller represents this limited functionality or
96 * not.
97 *
98 * This method succeeds (and increments the number of callers) only if the
99 * current object's state is Ready. Otherwise, it will return E_ACCESSDENIED
100 * to indicate that the object is not operational. There are two exceptions
101 * from this rule:
102 * <ol>
103 * <li>If the @a aLimited argument is |true|, then this method will also
104 * succeed if the object's state is Limited (or Ready, of course).
105 * </li>
106 * <li>If this method is called from the same thread that placed
107 * the object to InInit or InUninit state (i.e. either from within the
108 * AutoInitSpan or AutoUninitSpan scope), it will succeed as well (but
109 * will not increase the number of callers).
110 * </li>
111 * </ol>
112 *
113 * Normally, calling addCaller() never blocks. However, if this method is
114 * called by a thread created from within the AutoInitSpan scope and this
115 * scope is still active (i.e. the object state is InInit), it will block
116 * until the AutoInitSpan destructor signals that it has finished
117 * initialization.
118 *
119 * When this method returns a failure, the caller must not use the object
120 * and should return the failed result code to its own caller.
121 *
122 * @param aLimited |true| to add a limited caller.
123 *
124 * @return S_OK on success or E_ACCESSDENIED on failure.
125 *
126 * @sa #releaseCaller()
127 */
128HRESULT ObjectState::addCaller(bool aLimited /* = false */)
129{
130 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
131
132 HRESULT rc = E_ACCESSDENIED;
133
134 if (mState == Ready || (aLimited && mState == Limited))
135 {
136 /* if Ready or allows Limited, increase the number of callers */
137 ++mCallers;
138 rc = S_OK;
139 }
140 else
141 if (mState == InInit || mState == InUninit)
142 {
143 if (mStateChangeThread == RTThreadSelf())
144 {
145 /* Called from the same thread that is doing AutoInitSpan or
146 * AutoUninitSpan, just succeed */
147 rc = S_OK;
148 }
149 else if (mState == InInit)
150 {
151 /* addCaller() is called by a "child" thread while the "parent"
152 * thread is still doing AutoInitSpan/AutoReinitSpan, so wait for
153 * the state to become either Ready/Limited or InitFailed (in
154 * case of init failure).
155 *
156 * Note that we increase the number of callers anyway -- to
157 * prevent AutoUninitSpan from early completion if we are
158 * still not scheduled to pick up the posted semaphore when
159 * uninit() is called.
160 */
161 ++mCallers;
162
163 /* lazy semaphore creation */
164 if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
165 {
166 RTSemEventMultiCreate(&mInitUninitSem);
167 Assert(mInitUninitWaiters == 0);
168 }
169
170 ++mInitUninitWaiters;
171
172 LogFlowThisFunc(("Waiting for AutoInitSpan/AutoReinitSpan to finish...\n"));
173
174 stateLock.release();
175 RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
176 stateLock.acquire();
177
178 if (--mInitUninitWaiters == 0)
179 {
180 /* destroy the semaphore since no more necessary */
181 RTSemEventMultiDestroy(mInitUninitSem);
182 mInitUninitSem = NIL_RTSEMEVENTMULTI;
183 }
184
185 if (mState == Ready || (aLimited && mState == Limited))
186 rc = S_OK;
187 else
188 {
189 Assert(mCallers != 0);
190 --mCallers;
191 if (mCallers == 0 && mState == InUninit)
192 {
193 /* inform AutoUninitSpan ctor there are no more callers */
194 RTSemEventSignal(mZeroCallersSem);
195 }
196 }
197 }
198 }
199
200 if (FAILED(rc))
201 {
202 if (mState == Limited)
203 rc = mObj->setError(rc, "The object functionality is limited");
204 else if (FAILED(mFailedRC) && mFailedRC != E_ACCESSDENIED)
205 {
206 /* replay recorded error information */
207 if (mpFailedEI)
208 ErrorInfoKeeper eik(*mpFailedEI);
209 rc = mFailedRC;
210 }
211 else
212 rc = mObj->setError(rc, "The object is not ready");
213 }
214
215 return rc;
216}
217
218/**
219 * Decreases the number of calls to this object by one.
220 *
221 * Must be called after every #addCaller() when protecting the object
222 * from uninitialization is no more necessary.
223 */
224void ObjectState::releaseCaller()
225{
226 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
227
228 if (mState == Ready || mState == Limited)
229 {
230 /* if Ready or Limited, decrease the number of callers */
231 AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
232 --mCallers;
233
234 return;
235 }
236
237 if (mState == InInit || mState == InUninit)
238 {
239 if (mStateChangeThread == RTThreadSelf())
240 {
241 /* Called from the same thread that is doing AutoInitSpan or
242 * AutoUninitSpan: just succeed */
243 return;
244 }
245
246 if (mState == InUninit)
247 {
248 /* the caller is being released after AutoUninitSpan has begun */
249 AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
250 --mCallers;
251
252 if (mCallers == 0)
253 /* inform the Auto*UninitSpan ctor there are no more callers */
254 RTSemEventSignal(mZeroCallersSem);
255
256 return;
257 }
258 }
259
260 AssertMsgFailed(("mState = %d!", mState));
261}
262
263bool ObjectState::autoInitSpanConstructor(ObjectState::State aExpectedState)
264{
265 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
266
267 mFailedRC = S_OK;
268 if (mpFailedEI)
269 {
270 delete mpFailedEI;
271 mpFailedEI = NULL;
272 }
273
274 if (mState == aExpectedState)
275 {
276 setState(InInit);
277 return true;
278 }
279 else
280 return false;
281}
282
283void ObjectState::autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *apFailedEI)
284{
285 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
286
287 Assert(mState == InInit);
288
289 if (mCallers > 0 && mInitUninitWaiters > 0)
290 {
291 /* We have some pending addCaller() calls on other threads (created
292 * during InInit), signal that InInit is finished and they may go on. */
293 RTSemEventMultiSignal(mInitUninitSem);
294 }
295
296 if (aNewState == InitFailed || aNewState == Limited)
297 {
298 mFailedRC = aFailedRC;
299 /* apFailedEI may be NULL, when there is no explicit setFailed() or
300 * setLimited() call, which also implies that aFailedRC is S_OK.
301 * This case is used by objects (the majority) which don't want
302 * delayed error signalling. */
303 mpFailedEI = apFailedEI;
304 }
305 else
306 {
307 Assert(SUCCEEDED(aFailedRC));
308 Assert(apFailedEI == NULL);
309 Assert(mpFailedEI == NULL);
310 }
311
312 setState(aNewState);
313}
314
315ObjectState::State ObjectState::autoUninitSpanConstructor(bool fTry)
316{
317 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
318
319 Assert(mState != InInit);
320
321 if (mState == NotReady)
322 {
323 /* do nothing if already uninitialized */
324 return mState;
325 }
326 else if (mState == InUninit)
327 {
328 /* Another thread has already started uninitialization, wait for its
329 * completion. This is necessary to make sure that when this method
330 * returns, the object state is well-defined (NotReady). */
331
332 if (fTry)
333 return Ready;
334
335 /* lazy semaphore creation */
336 if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
337 {
338 RTSemEventMultiCreate(&mInitUninitSem);
339 Assert(mInitUninitWaiters == 0);
340 }
341 ++mInitUninitWaiters;
342
343 LogFlowFunc(("{%p}: Waiting for AutoUninitSpan to finish...\n", mObj));
344
345 stateLock.release();
346 RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
347 stateLock.acquire();
348
349 if (--mInitUninitWaiters == 0)
350 {
351 /* destroy the semaphore since no more necessary */
352 RTSemEventMultiDestroy(mInitUninitSem);
353 mInitUninitSem = NIL_RTSEMEVENTMULTI;
354 }
355
356 /* the other thread set it to NotReady */
357 return mState;
358 }
359
360 /* go to InUninit to prevent from adding new callers */
361 setState(InUninit);
362
363 /* wait for already existing callers to drop to zero */
364 if (mCallers > 0)
365 {
366 if (fTry)
367 return Ready;
368
369 /* lazy creation */
370 Assert(mZeroCallersSem == NIL_RTSEMEVENT);
371 RTSemEventCreate(&mZeroCallersSem);
372
373 /* wait until remaining callers release the object */
374 LogFlowFunc(("{%p}: Waiting for callers (%d) to drop to zero...\n",
375 mObj, mCallers));
376
377 stateLock.release();
378 RTSemEventWait(mZeroCallersSem, RT_INDEFINITE_WAIT);
379 }
380 return mState;
381}
382
383void ObjectState::autoUninitSpanDestructor()
384{
385 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
386
387 Assert(mState == InUninit);
388
389 setState(NotReady);
390}
391
392
393void ObjectState::setState(ObjectState::State aState)
394{
395 Assert(mState != aState);
396 mState = aState;
397 mStateChangeThread = RTThreadSelf();
398}
399
400
401////////////////////////////////////////////////////////////////////////////////
402//
403// AutoInitSpan methods
404//
405////////////////////////////////////////////////////////////////////////////////
406
407/**
408 * Creates a smart initialization span object that places the object to
409 * InInit state.
410 *
411 * Please see the AutoInitSpan class description for more info.
412 *
413 * @param aObj |this| pointer of the managed VirtualBoxBase object whose
414 * init() method is being called.
415 * @param aResult Default initialization result.
416 */
417AutoInitSpan::AutoInitSpan(VirtualBoxBase *aObj,
418 Result aResult /* = Failed */)
419 : mObj(aObj),
420 mResult(aResult),
421 mOk(false),
422 mFailedRC(S_OK),
423 mpFailedEI(NULL)
424{
425 Assert(mObj);
426 mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::NotReady);
427 AssertReturnVoid(mOk);
428}
429
430/**
431 * Places the managed VirtualBoxBase object to Ready/Limited state if the
432 * initialization succeeded or partly succeeded, or places it to InitFailed
433 * state and calls the object's uninit() method.
434 *
435 * Please see the AutoInitSpan class description for more info.
436 */
437AutoInitSpan::~AutoInitSpan()
438{
439 /* if the state was other than NotReady, do nothing */
440 if (!mOk)
441 {
442 Assert(SUCCEEDED(mFailedRC));
443 Assert(mpFailedEI == NULL);
444 return;
445 }
446
447 ObjectState::State newState;
448 if (mResult == Succeeded)
449 newState = ObjectState::Ready;
450 else if (mResult == Limited)
451 newState = ObjectState::Limited;
452 else
453 newState = ObjectState::InitFailed;
454 mObj->getObjectState().autoInitSpanDestructor(newState, mFailedRC, mpFailedEI);
455 mFailedRC = S_OK;
456 mpFailedEI = NULL; /* now owned by ObjectState instance */
457 if (newState == ObjectState::InitFailed)
458 {
459 /* call uninit() to let the object uninit itself after failed init() */
460 mObj->uninit();
461 }
462}
463
464// AutoReinitSpan methods
465////////////////////////////////////////////////////////////////////////////////
466
467/**
468 * Creates a smart re-initialization span object and places the object to
469 * InInit state.
470 *
471 * Please see the AutoInitSpan class description for more info.
472 *
473 * @param aObj |this| pointer of the managed VirtualBoxBase object whose
474 * re-initialization method is being called.
475 */
476AutoReinitSpan::AutoReinitSpan(VirtualBoxBase *aObj)
477 : mObj(aObj),
478 mSucceeded(false),
479 mOk(false)
480{
481 Assert(mObj);
482 mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::Limited);
483 AssertReturnVoid(mOk);
484}
485
486/**
487 * Places the managed VirtualBoxBase object to Ready state if the
488 * re-initialization succeeded (i.e. #setSucceeded() has been called) or back to
489 * Limited state otherwise.
490 *
491 * Please see the AutoInitSpan class description for more info.
492 */
493AutoReinitSpan::~AutoReinitSpan()
494{
495 /* if the state was other than Limited, do nothing */
496 if (!mOk)
497 return;
498
499 ObjectState::State newState;
500 if (mSucceeded)
501 newState = ObjectState::Ready;
502 else
503 newState = ObjectState::Limited;
504 mObj->getObjectState().autoInitSpanDestructor(newState, S_OK, NULL);
505 /* If later AutoReinitSpan can truly fail (today there is no way) then
506 * in this place there needs to be an mObj->uninit() call just like in
507 * the AutoInitSpan destructor. In that case it might make sense to
508 * let AutoReinitSpan inherit from AutoInitSpan, as the code can be
509 * made (almost) identical. */
510}
511
512// AutoUninitSpan methods
513////////////////////////////////////////////////////////////////////////////////
514
515/**
516 * Creates a smart uninitialization span object and places this object to
517 * InUninit state.
518 *
519 * Please see the AutoInitSpan class description for more info.
520 *
521 * @note This method blocks the current thread execution until the number of
522 * callers of the managed VirtualBoxBase object drops to zero!
523 *
524 * @param aObj |this| pointer of the VirtualBoxBase object whose uninit()
525 * method is being called.
526 * @param fTry @c true if the wait for other callers should be skipped,
527 * requiring checking if the uninit span is actually operational.
528 */
529AutoUninitSpan::AutoUninitSpan(VirtualBoxBase *aObj, bool fTry /* = false */)
530 : mObj(aObj),
531 mInitFailed(false),
532 mUninitDone(false),
533 mUninitFailed(false)
534{
535 Assert(mObj);
536 ObjectState::State state;
537 state = mObj->getObjectState().autoUninitSpanConstructor(fTry);
538 if (state == ObjectState::InitFailed)
539 mInitFailed = true;
540 else if (state == ObjectState::NotReady)
541 mUninitDone = true;
542 else if (state == ObjectState::Ready)
543 mUninitFailed = true;
544}
545
546/**
547 * Places the managed VirtualBoxBase object to the NotReady state.
548 */
549AutoUninitSpan::~AutoUninitSpan()
550{
551 /* do nothing if already uninitialized */
552 if (mUninitDone || mUninitFailed)
553 return;
554
555 mObj->getObjectState().autoUninitSpanDestructor();
556}
557
558/**
559 * Marks the uninitializion as succeeded.
560 *
561 * Same as the destructor, and makes the destructor do nothing.
562 */
563void AutoUninitSpan::setSucceeded()
564{
565 /* do nothing if already uninitialized */
566 if (mUninitDone || mUninitFailed)
567 return;
568
569 mObj->getObjectState().autoUninitSpanDestructor();
570 mUninitDone = true;
571}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette