VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/ProgressImpl.cpp@ 53390

Last change on this file since 53390 was 51687, checked in by vboxsync, 11 years ago

Main/Guid+Progress: big cleanup of Guid.h and some Progress cleanup, mostly removing the totally unused aId parameter of the init method and by deleting long gone initi/uninit methods

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.3 KB
Line 
1/* $Id: ProgressImpl.cpp 51687 2014-06-23 11:23:59Z vboxsync $ */
2/** @file
3 *
4 * VirtualBox Progress COM class implementation
5 */
6
7/*
8 * Copyright (C) 2006-2014 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include <iprt/types.h>
20
21
22#if defined(VBOX_WITH_XPCOM)
23#include <nsIServiceManager.h>
24#include <nsIExceptionService.h>
25#include <nsCOMPtr.h>
26#endif /* defined(VBOX_WITH_XPCOM) */
27
28#include "ProgressImpl.h"
29
30#if !defined(VBOX_COM_INPROC)
31# include "VirtualBoxImpl.h"
32#endif
33#include "VirtualBoxErrorInfoImpl.h"
34
35#include "Logging.h"
36
37#include <iprt/time.h>
38#include <iprt/semaphore.h>
39#include <iprt/cpp/utils.h>
40
41#include <VBox/err.h>
42#include "AutoCaller.h"
43
44
45Progress::Progress()
46#if !defined(VBOX_COM_INPROC)
47 : mParent(NULL)
48#endif
49{
50}
51
52Progress::~Progress()
53{
54}
55
56
57// IProgress properties
58/////////////////////////////////////////////////////////////////////////////
59
60HRESULT Progress::getId(com::Guid &aId)
61{
62 /* mId is constant during life time, no need to lock */
63 aId = mId;
64
65 return S_OK;
66}
67
68HRESULT Progress::getDescription(com::Utf8Str &aDescription)
69{
70 /* mDescription is constant during life time, no need to lock */
71 aDescription = mDescription;
72
73 return S_OK;
74}
75HRESULT Progress::getInitiator(ComPtr<IUnknown> &aInitiator)
76{
77
78 /* mInitiator/mParent are constant during life time, no need to lock */
79
80#if !defined(VBOX_COM_INPROC)
81 if (mInitiator)
82 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
83 else
84 {
85 ComObjPtr<VirtualBox> pVirtualBox(mParent);
86 pVirtualBox.queryInterfaceTo(aInitiator.asOutParam());
87 }
88#else
89 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
90#endif
91
92 return S_OK;
93}
94
95HRESULT Progress::getCancelable(BOOL *aCancelable)
96{
97
98 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
99
100 *aCancelable = mCancelable;
101
102 return S_OK;
103}
104
105/**
106 * Internal helper to compute the total percent value based on the member values and
107 * returns it as a "double". This is used both by GetPercent (which returns it as a
108 * rounded ULONG) and GetTimeRemaining().
109 *
110 * Requires locking by the caller!
111 *
112 * @return fractional percentage as a double value.
113 */
114double Progress::i_calcTotalPercent()
115{
116 // avoid division by zero
117 if (m_ulTotalOperationsWeight == 0)
118 return 0;
119
120 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
121 + ((double)m_ulOperationPercent *
122 (double)m_ulCurrentOperationWeight / (double)100) // plus partial weight of the current operation
123 ) * (double)100 / (double)m_ulTotalOperationsWeight;
124
125 return dPercent;
126}
127
128/**
129 * Internal helper for automatically timing out the operation.
130 *
131 * The caller should hold the object write lock.
132 */
133void Progress::i_checkForAutomaticTimeout(void)
134{
135 if ( m_cMsTimeout
136 && mCancelable
137 && !mCanceled
138 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout
139 )
140 Cancel();
141}
142
143HRESULT Progress::getTimeRemaining(LONG *aTimeRemaining)
144{
145 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
146
147 if (mCompleted)
148 *aTimeRemaining = 0;
149 else
150 {
151 double dPercentDone = i_calcTotalPercent();
152 if (dPercentDone < 1)
153 *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
154 else
155 {
156 uint64_t ullTimeNow = RTTimeMilliTS();
157 uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
158 uint64_t ullTimeTotal = (uint64_t)(ullTimeElapsed * 100 / dPercentDone);
159 uint64_t ullTimeRemaining = ullTimeTotal - ullTimeElapsed;
160
161// LogFunc(("dPercentDone = %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
162// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
163
164 *aTimeRemaining = (LONG)(ullTimeRemaining / 1000);
165 }
166 }
167
168 return S_OK;
169}
170
171HRESULT Progress::getPercent(ULONG *aPercent)
172{
173 i_checkForAutomaticTimeout();
174
175 /* checkForAutomaticTimeout requires a write lock. */
176 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
177
178 if (mCompleted && SUCCEEDED(mResultCode))
179 *aPercent = 100;
180 else
181 {
182 ULONG ulPercent = (ULONG)i_calcTotalPercent();
183 // do not report 100% until we're really really done with everything as the Qt GUI dismisses progress dialogs in that case
184 if ( ulPercent == 100
185 && ( m_ulOperationPercent < 100
186 || (m_ulCurrentOperation < m_cOperations -1)
187 )
188 )
189 *aPercent = 99;
190 else
191 *aPercent = ulPercent;
192 }
193
194 i_checkForAutomaticTimeout();
195
196 return S_OK;
197}
198
199HRESULT Progress::getCompleted(BOOL *aCompleted)
200{
201 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
202
203 *aCompleted = mCompleted;
204
205 return S_OK;
206}
207
208HRESULT Progress::getCanceled(BOOL *aCanceled)
209{
210 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
211
212 *aCanceled = mCanceled;
213
214 return S_OK;
215}
216
217HRESULT Progress::getResultCode(LONG *aResultCode)
218{
219 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
220
221 if (!mCompleted)
222 return setError(E_FAIL,
223 tr("Result code is not available, operation is still in progress"));
224
225 *aResultCode = mResultCode;
226
227 return S_OK;
228}
229
230HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
231{
232 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
233
234 if (!mCompleted)
235 return setError(E_FAIL,
236 tr("Error info is not available, operation is still in progress"));
237
238 mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
239
240 return S_OK;
241}
242
243HRESULT Progress::getOperationCount(ULONG *aOperationCount)
244{
245 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
246
247 *aOperationCount = m_cOperations;
248
249 return S_OK;
250}
251
252HRESULT Progress::getOperation(ULONG *aOperation)
253{
254 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
255
256 *aOperation = m_ulCurrentOperation;
257
258 return S_OK;
259}
260
261HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
262{
263
264 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
265
266 aOperationDescription = m_operationDescription;
267
268 return S_OK;
269}
270
271HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
272{
273 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
274
275 if (mCompleted && SUCCEEDED(mResultCode))
276 *aOperationPercent = 100;
277 else
278 *aOperationPercent = m_ulOperationPercent;
279
280 return S_OK;
281}
282
283HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
284{
285 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
286
287 *aOperationWeight = m_ulCurrentOperationWeight;
288
289 return S_OK;
290}
291
292HRESULT Progress::setTimeout(ULONG aTimeout)
293{
294 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
295
296 if (!mCancelable)
297 return setError(VBOX_E_INVALID_OBJECT_STATE,
298 tr("Operation cannot be canceled"));
299
300 LogThisFunc(("%#x => %#x\n", m_cMsTimeout, aTimeout));
301 m_cMsTimeout = aTimeout;
302 return S_OK;
303}
304
305HRESULT Progress::getTimeout(ULONG *aTimeout)
306{
307 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
308
309 *aTimeout = m_cMsTimeout;
310 return S_OK;
311}
312
313// public methods only for internal purposes
314////////////////////////////////////////////////////////////////////////////////
315
316/**
317 * Sets the cancelation callback, checking for cancelation first.
318 *
319 * @returns Success indicator.
320 * @retval true on success.
321 * @retval false if the progress object has already been canceled or is in an
322 * invalid state
323 *
324 * @param pfnCallback The function to be called upon cancelation.
325 * @param pvUser The callback argument.
326 */
327bool Progress::i_setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
328{
329 AutoCaller autoCaller(this);
330 AssertComRCReturn(autoCaller.rc(), false);
331
332 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
333
334 i_checkForAutomaticTimeout();
335 if (mCanceled)
336 return false;
337
338 m_pvCancelUserArg = pvUser;
339 m_pfnCancelCallback = pfnCallback;
340 return true;
341}
342
343HRESULT Progress::FinalConstruct()
344{
345 mCancelable = FALSE;
346 mCompleted = FALSE;
347 mCanceled = FALSE;
348 mResultCode = S_OK;
349
350 m_cOperations
351 = m_ulTotalOperationsWeight
352 = m_ulOperationsCompletedWeight
353 = m_ulCurrentOperation
354 = m_ulCurrentOperationWeight
355 = m_ulOperationPercent
356 = m_cMsTimeout
357 = 0;
358
359 // get creation timestamp
360 m_ullTimestamp = RTTimeMilliTS();
361
362 m_pfnCancelCallback = NULL;
363 m_pvCancelUserArg = NULL;
364
365 HRESULT rc = Progress::BaseFinalConstruct();
366 if (FAILED(rc)) return rc;
367
368 mCompletedSem = NIL_RTSEMEVENTMULTI;
369 mWaitersCount = 0;
370
371 return S_OK;
372}
373
374void Progress::FinalRelease()
375{
376 uninit();
377 BaseFinalRelease();
378}
379
380// public initializer/uninitializer for internal purposes only
381////////////////////////////////////////////////////////////////////////////////
382
383/**
384 * Initializes the normal progress object. With this variant, one can have
385 * an arbitrary number of sub-operation which IProgress can analyze to
386 * have a weighted progress computed.
387 *
388 * For example, say that one IProgress is supposed to track the cloning
389 * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
390 * and each of these hard disks should be one sub-operation of the IProgress.
391 *
392 * Obviously the progress would be misleading if the progress displayed 50%
393 * after the smaller image was cloned and would then take much longer for
394 * the second half.
395 *
396 * With weighted progress, one can invoke the following calls:
397 *
398 * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
399 * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
400 * in ulFirstOperationWeight = 100 for the first sub-operation
401 *
402 * 2) Then keep calling setCurrentOperationProgress() with a percentage
403 * for the first image; the total progress will increase up to a value
404 * of 9% (100MB / 1100MB * 100%).
405 *
406 * 3) Then call setNextOperation with the second weight (1000 for the megabytes
407 * of the second disk).
408 *
409 * 4) Then keep calling setCurrentOperationProgress() with a percentage for
410 * the second image, where 100% of the operation will then yield a 100%
411 * progress of the entire task.
412 *
413 * Weighting is optional; you can simply assign a weight of 1 to each operation
414 * and pass ulTotalOperationsWeight == cOperations to this constructor (but
415 * for that variant and for backwards-compatibility a simpler constructor exists
416 * in ProgressImpl.h as well).
417 *
418 * Even simpler, if you need no sub-operations at all, pass in cOperations =
419 * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
420 *
421 * @param aParent Parent object (only for server-side Progress objects).
422 * @param aInitiator Initiator of the task (for server-side objects. Can be
423 * NULL which means initiator = parent, otherwise must not
424 * be NULL).
425 * @param aDescription Overall task description.
426 * @param aCancelable Flag whether the task maybe canceled.
427 * @param cOperations Number of operations within this task (at least 1).
428 * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
429 * what is later passed with each subsequent setNextOperation() call.
430 * @param bstrFirstOperationDescription Description of the first operation.
431 * @param ulFirstOperationWeight Weight of first sub-operation.
432 */
433HRESULT Progress::init(
434#if !defined(VBOX_COM_INPROC)
435 VirtualBox *aParent,
436#endif
437 IUnknown *aInitiator,
438 Utf8Str aDescription,
439 BOOL aCancelable,
440 ULONG cOperations,
441 ULONG ulTotalOperationsWeight,
442 Utf8Str aFirstOperationDescription,
443 ULONG ulFirstOperationWeight)
444{
445 LogFlowThisFunc(("aDescription=\"%s\", cOperations=%d, ulTotalOperationsWeight=%d, aFirstOperationDescription=\"%s\", ulFirstOperationWeight=%d\n",
446 aDescription.c_str(),
447 cOperations,
448 ulTotalOperationsWeight,
449 aFirstOperationDescription.c_str(),
450 ulFirstOperationWeight));
451
452 AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
453
454 /* Enclose the state transition NotReady->InInit->Ready */
455 AutoInitSpan autoInitSpan(this);
456 AssertReturn(autoInitSpan.isOk(), E_FAIL);
457
458 HRESULT rc = S_OK;
459
460// rc = Progress::init(
461//#if !defined(VBOX_COM_INPROC)
462// aParent,
463//#endif
464// aInitiator, aDescription, FALSE, aId);
465// NA
466#if !defined(VBOX_COM_INPROC)
467 AssertReturn(aParent, E_INVALIDARG);
468#else
469 AssertReturn(aInitiator, E_INVALIDARG);
470#endif
471
472#if !defined(VBOX_COM_INPROC)
473 /* share parent weakly */
474 unconst(mParent) = aParent;
475#endif
476
477#if !defined(VBOX_COM_INPROC)
478 /* assign (and therefore addref) initiator only if it is not VirtualBox
479 * (to avoid cycling); otherwise mInitiator will remain null which means
480 * that it is the same as the parent */
481 if (aInitiator)
482 {
483 ComObjPtr<VirtualBox> pVirtualBox(mParent);
484 if (!(pVirtualBox == aInitiator))
485 unconst(mInitiator) = aInitiator;
486 }
487#else
488 unconst(mInitiator) = aInitiator;
489#endif
490
491 unconst(mId).create();
492
493#if !defined(VBOX_COM_INPROC)
494 /* add to the global collection of progress operations (note: after
495 * creating mId) */
496 mParent->i_addProgress(this);
497#endif
498
499 unconst(mDescription) = aDescription;
500
501
502// end of assertion
503
504
505 if (FAILED(rc)) return rc;
506
507 mCancelable = aCancelable;
508
509 m_cOperations = cOperations;
510 m_ulTotalOperationsWeight = ulTotalOperationsWeight;
511 m_ulOperationsCompletedWeight = 0;
512 m_ulCurrentOperation = 0;
513 m_operationDescription = aFirstOperationDescription;
514 m_ulCurrentOperationWeight = ulFirstOperationWeight;
515 m_ulOperationPercent = 0;
516
517 int vrc = RTSemEventMultiCreate(&mCompletedSem);
518 ComAssertRCRet(vrc, E_FAIL);
519
520 RTSemEventMultiReset(mCompletedSem);
521
522 /* Confirm a successful initialization when it's the case */
523 if (SUCCEEDED(rc))
524 autoInitSpan.setSucceeded();
525
526 return rc;
527}
528
529/**
530 * Initializes the sub-progress object that represents a specific operation of
531 * the whole task.
532 *
533 * Objects initialized with this method are then combined together into the
534 * single task using a Progress instance, so it doesn't require the
535 * parent, initiator, description and doesn't create an ID. Note that calling
536 * respective getter methods on an object initialized with this method is
537 * useless. Such objects are used only to provide a separate wait semaphore and
538 * store individual operation descriptions.
539 *
540 * @param aCancelable Flag whether the task maybe canceled.
541 * @param aOperationCount Number of sub-operations within this task (at least 1).
542 * @param aOperationDescription Description of the individual operation.
543 */
544HRESULT Progress::init(BOOL aCancelable,
545 ULONG aOperationCount,
546 Utf8Str aOperationDescription)
547{
548 LogFlowThisFunc(("aOperationDescription=\"%s\"\n", aOperationDescription.c_str()));
549
550 /* Enclose the state transition NotReady->InInit->Ready */
551 AutoInitSpan autoInitSpan(this);
552 AssertReturn(autoInitSpan.isOk(), E_FAIL);
553
554 HRESULT rc = S_OK;
555 /* Guarantees subclasses call this method at the proper time */
556 NOREF(autoInitSpan);
557
558 if (FAILED(rc)) return rc;
559
560 mCancelable = aCancelable;
561
562 // for this variant we assume for now that all operations are weighed "1"
563 // and equal total weight = operation count
564 m_cOperations = aOperationCount;
565 m_ulTotalOperationsWeight = aOperationCount;
566 m_ulOperationsCompletedWeight = 0;
567 m_ulCurrentOperation = 0;
568 m_operationDescription = aOperationDescription;
569 m_ulCurrentOperationWeight = 1;
570 m_ulOperationPercent = 0;
571
572 int vrc = RTSemEventMultiCreate(&mCompletedSem);
573 ComAssertRCRet(vrc, E_FAIL);
574
575 RTSemEventMultiReset(mCompletedSem);
576
577 /* Confirm a successful initialization when it's the case */
578 if (SUCCEEDED(rc))
579 autoInitSpan.setSucceeded();
580
581 return rc;
582}
583
584
585/**
586 * Uninitializes the instance and sets the ready flag to FALSE.
587 *
588 * Called either from FinalRelease() or by the parent when it gets destroyed.
589 */
590void Progress::uninit()
591{
592 LogFlowThisFunc(("\n"));
593
594 /* Enclose the state transition Ready->InUninit->NotReady */
595 AutoUninitSpan autoUninitSpan(this);
596 if (autoUninitSpan.uninitDone())
597 return;
598
599 /* wake up all threads still waiting on occasion */
600 if (mWaitersCount > 0)
601 {
602 LogFlow(("WARNING: There are still %d threads waiting for '%s' completion!\n",
603 mWaitersCount, mDescription.c_str()));
604 RTSemEventMultiSignal(mCompletedSem);
605 }
606
607 RTSemEventMultiDestroy(mCompletedSem);
608
609 /* release initiator (effective only if mInitiator has been assigned in
610 * * init()) */
611 unconst(mInitiator).setNull();
612
613#if !defined(VBOX_COM_INPROC)
614 if (mParent)
615 {
616 /* remove the added progress on failure to complete the initialization */
617 if (autoUninitSpan.initFailed() && mId.isValid() && !mId.isZero())
618 mParent->i_removeProgress(mId.ref());
619
620 unconst(mParent) = NULL;
621 }
622#endif
623}
624
625
626// IProgress properties
627/////////////////////////////////////////////////////////////////////////////
628
629// IProgress methods
630/////////////////////////////////////////////////////////////////////////////
631
632/**
633 * @note XPCOM: when this method is not called on the main XPCOM thread, it
634 * simply blocks the thread until mCompletedSem is signalled. If the
635 * thread has its own event queue (hmm, what for?) that it must run, then
636 * calling this method will definitely freeze event processing.
637 */
638HRESULT Progress::waitForCompletion(LONG aTimeout)
639{
640 LogFlowThisFuncEnter();
641 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
642
643 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
644
645 /* if we're already completed, take a shortcut */
646 if (!mCompleted)
647 {
648 int vrc = VINF_SUCCESS;
649 bool fForever = aTimeout < 0;
650 int64_t timeLeft = aTimeout;
651 int64_t lastTime = RTTimeMilliTS();
652
653 while (!mCompleted && (fForever || timeLeft > 0))
654 {
655 mWaitersCount++;
656 alock.release();
657 vrc = RTSemEventMultiWait(mCompletedSem,
658 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
659 alock.acquire();
660 mWaitersCount--;
661
662 /* the last waiter resets the semaphore */
663 if (mWaitersCount == 0)
664 RTSemEventMultiReset(mCompletedSem);
665
666 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
667 break;
668
669 if (!fForever)
670 {
671 int64_t now = RTTimeMilliTS();
672 timeLeft -= now - lastTime;
673 lastTime = now;
674 }
675 }
676
677 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
678 return setError(VBOX_E_IPRT_ERROR,
679 tr("Failed to wait for the task completion (%Rrc)"),
680 vrc);
681 }
682
683 LogFlowThisFuncLeave();
684
685 return S_OK;
686}
687
688/**
689 * @note XPCOM: when this method is not called on the main XPCOM thread, it
690 * simply blocks the thread until mCompletedSem is signalled. If the
691 * thread has its own event queue (hmm, what for?) that it must run, then
692 * calling this method will definitely freeze event processing.
693 */
694HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
695
696{
697 LogFlowThisFuncEnter();
698 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
699
700 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
701
702 CheckComArgExpr(aOperation, aOperation < m_cOperations);
703
704 /* if we're already completed or if the given operation is already done,
705 * then take a shortcut */
706 if ( !mCompleted
707 && aOperation >= m_ulCurrentOperation)
708 {
709 int vrc = VINF_SUCCESS;
710 bool fForever = aTimeout < 0;
711 int64_t timeLeft = aTimeout;
712 int64_t lastTime = RTTimeMilliTS();
713
714 while ( !mCompleted && aOperation >= m_ulCurrentOperation
715 && (fForever || timeLeft > 0))
716 {
717 mWaitersCount ++;
718 alock.release();
719 vrc = RTSemEventMultiWait(mCompletedSem,
720 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
721 alock.acquire();
722 mWaitersCount--;
723
724 /* the last waiter resets the semaphore */
725 if (mWaitersCount == 0)
726 RTSemEventMultiReset(mCompletedSem);
727
728 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
729 break;
730
731 if (!fForever)
732 {
733 int64_t now = RTTimeMilliTS();
734 timeLeft -= now - lastTime;
735 lastTime = now;
736 }
737 }
738
739 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
740 return setError(E_FAIL,
741 tr("Failed to wait for the operation completion (%Rrc)"),
742 vrc);
743 }
744
745 LogFlowThisFuncLeave();
746
747 return S_OK;
748}
749
750HRESULT Progress::waitForAsyncProgressCompletion(const ComPtr<IProgress> &aPProgressAsync)
751{
752 LogFlowThisFuncEnter();
753
754 /* Note: we don't lock here, cause we just using public methods. */
755
756 HRESULT rc = S_OK;
757 BOOL fCancelable = FALSE;
758 BOOL fCompleted = FALSE;
759 BOOL fCanceled = FALSE;
760 ULONG prevPercent = UINT32_MAX;
761 ULONG currentPercent = 0;
762 ULONG cOp = 0;
763 /* Is the async process cancelable? */
764 rc = aPProgressAsync->COMGETTER(Cancelable)(&fCancelable);
765 if (FAILED(rc)) return rc;
766 /* Loop as long as the sync process isn't completed. */
767 while (SUCCEEDED(aPProgressAsync->COMGETTER(Completed(&fCompleted))))
768 {
769 /* We can forward any cancel request to the async process only when
770 * it is cancelable. */
771 if (fCancelable)
772 {
773 rc = COMGETTER(Canceled)(&fCanceled);
774 if (FAILED(rc)) return rc;
775 if (fCanceled)
776 {
777 rc = aPProgressAsync->Cancel();
778 if (FAILED(rc)) return rc;
779 }
780 }
781 /* Even if the user canceled the process, we have to wait until the
782 async task has finished his work (cleanup and such). Otherwise there
783 will be sync trouble (still wrong state, dead locks, ...) on the
784 used objects. So just do nothing, but wait for the complete
785 notification. */
786 if (!fCanceled)
787 {
788 /* Check if the current operation has changed. It is also possible that
789 * in the meantime more than one async operation was finished. So we
790 * have to loop as long as we reached the same operation count. */
791 ULONG curOp;
792 for (;;)
793 {
794 rc = aPProgressAsync->COMGETTER(Operation(&curOp));
795 if (FAILED(rc)) return rc;
796 if (cOp != curOp)
797 {
798 Bstr bstr;
799 ULONG currentWeight;
800 rc = aPProgressAsync->COMGETTER(OperationDescription(bstr.asOutParam()));
801 if (FAILED(rc)) return rc;
802 rc = aPProgressAsync->COMGETTER(OperationWeight(&currentWeight));
803 if (FAILED(rc)) return rc;
804 rc = SetNextOperation(bstr.raw(), currentWeight);
805 if (FAILED(rc)) return rc;
806 ++cOp;
807 }
808 else
809 break;
810 }
811
812 rc = aPProgressAsync->COMGETTER(OperationPercent(&currentPercent));
813 if (FAILED(rc)) return rc;
814 if (currentPercent != prevPercent)
815 {
816 prevPercent = currentPercent;
817 rc = SetCurrentOperationProgress(currentPercent);
818 if (FAILED(rc)) return rc;
819 }
820 }
821 if (fCompleted)
822 break;
823
824 /* Make sure the loop is not too tight */
825 rc = aPProgressAsync->WaitForCompletion(100);
826 if (FAILED(rc)) return rc;
827 }
828
829 LogFlowThisFuncLeave();
830
831 return rc;
832}
833
834HRESULT Progress::cancel()
835{
836 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
837
838 if (!mCancelable)
839 return setError(VBOX_E_INVALID_OBJECT_STATE,
840 tr("Operation cannot be canceled"));
841
842 if (!mCanceled)
843 {
844 LogThisFunc(("Canceling\n"));
845 mCanceled = TRUE;
846 if (m_pfnCancelCallback)
847 m_pfnCancelCallback(m_pvCancelUserArg);
848
849 }
850 else
851 LogThisFunc(("Already canceled\n"));
852
853 return S_OK;
854}
855
856/**
857 * Updates the percentage value of the current operation.
858 *
859 * @param aPercent New percentage value of the operation in progress
860 * (in range [0, 100]).
861 */
862HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
863{
864 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
865
866 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
867
868 i_checkForAutomaticTimeout();
869 if (mCancelable && mCanceled)
870 {
871 Assert(!mCompleted);
872 return E_FAIL;
873 }
874 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
875
876 m_ulOperationPercent = aPercent;
877
878 return S_OK;
879}
880
881/**
882 * Signals that the current operation is successfully completed and advances to
883 * the next operation. The operation percentage is reset to 0.
884 *
885 * @param aOperationDescription Description of the next operation.
886 *
887 * @note The current operation must not be the last one.
888 */
889HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
890
891{
892 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
893
894 if (mCanceled)
895 return E_FAIL;
896 AssertReturn(!mCompleted, E_FAIL);
897 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
898
899 ++m_ulCurrentOperation;
900 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
901
902 m_operationDescription = aNextOperationDescription;
903 m_ulCurrentOperationWeight = aNextOperationsWeight;
904 m_ulOperationPercent = 0;
905
906 Log(("Progress::setNextOperation(%s): aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
907 m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
908
909 /* wake up all waiting threads */
910 if (mWaitersCount > 0)
911 RTSemEventMultiSignal(mCompletedSem);
912
913 return S_OK;
914}
915
916// public methods only for internal purposes
917/////////////////////////////////////////////////////////////////////////////
918
919/**
920 * Sets the internal result code and attempts to retrieve additional error
921 * info from the current thread. Gets called from Progress::notifyComplete(),
922 * but can be called again to override a previous result set with
923 * notifyComplete().
924 *
925 * @param aResultCode
926 */
927HRESULT Progress::i_setResultCode(HRESULT aResultCode)
928{
929 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
930
931 mResultCode = aResultCode;
932
933 HRESULT rc = S_OK;
934
935 if (FAILED(aResultCode))
936 {
937 /* try to import error info from the current thread */
938
939#if !defined(VBOX_WITH_XPCOM)
940
941 ComPtr<IErrorInfo> err;
942 rc = ::GetErrorInfo(0, err.asOutParam());
943 if (rc == S_OK && err)
944 {
945 rc = err.queryInterfaceTo(mErrorInfo.asOutParam());
946 if (SUCCEEDED(rc) && !mErrorInfo)
947 rc = E_FAIL;
948 }
949
950#else /* !defined(VBOX_WITH_XPCOM) */
951
952 nsCOMPtr<nsIExceptionService> es;
953 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
954 if (NS_SUCCEEDED(rc))
955 {
956 nsCOMPtr <nsIExceptionManager> em;
957 rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
958 if (NS_SUCCEEDED(rc))
959 {
960 ComPtr<nsIException> ex;
961 rc = em->GetCurrentException(ex.asOutParam());
962 if (NS_SUCCEEDED(rc) && ex)
963 {
964 rc = ex.queryInterfaceTo(mErrorInfo.asOutParam());
965 if (NS_SUCCEEDED(rc) && !mErrorInfo)
966 rc = E_FAIL;
967 }
968 }
969 }
970#endif /* !defined(VBOX_WITH_XPCOM) */
971
972 AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
973 rc, aResultCode));
974 }
975
976 return rc;
977}
978
979/**
980 * Marks the whole task as complete and sets the result code.
981 *
982 * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
983 * method will import the error info from the current thread and assign it to
984 * the errorInfo attribute (it will return an error if no info is available in
985 * such case).
986 *
987 * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
988 * the current operation is set to the last.
989 *
990 * Note that this method may be called only once for the given Progress object.
991 * Subsequent calls will assert.
992 *
993 * @param aResultCode Operation result code.
994 */
995HRESULT Progress::i_notifyComplete(HRESULT aResultCode)
996{
997 AutoCaller autoCaller(this);
998 AssertComRCReturnRC(autoCaller.rc());
999
1000 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1001
1002 AssertReturn(mCompleted == FALSE, E_FAIL);
1003
1004 LogFunc(("aResultCode=%d\n", aResultCode));
1005
1006 if (mCanceled && SUCCEEDED(aResultCode))
1007 aResultCode = E_FAIL;
1008
1009 HRESULT rc = i_setResultCode(aResultCode);
1010
1011 mCompleted = TRUE;
1012
1013 if (!FAILED(aResultCode))
1014 {
1015 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1016 m_ulOperationPercent = 100;
1017 }
1018
1019#if !defined VBOX_COM_INPROC
1020 /* remove from the global collection of pending progress operations */
1021 if (mParent)
1022 mParent->i_removeProgress(mId.ref());
1023#endif
1024
1025 /* wake up all waiting threads */
1026 if (mWaitersCount > 0)
1027 RTSemEventMultiSignal(mCompletedSem);
1028
1029 return rc;
1030}
1031
1032/**
1033 * Wrapper around Progress:notifyCompleteV.
1034 */
1035HRESULT Progress::i_notifyComplete(HRESULT aResultCode,
1036 const GUID &aIID,
1037 const char *pcszComponent,
1038 const char *aText,
1039 ...)
1040{
1041 va_list va;
1042 va_start(va, aText);
1043 HRESULT hrc = i_notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
1044 va_end(va);
1045 return hrc;
1046}
1047
1048/**
1049 * Marks the operation as complete and attaches full error info.
1050 *
1051 * See VirtualBoxBase::setError(HRESULT, const GUID &, const wchar_t
1052 * *, const char *, ...) for more info.
1053 *
1054 * @param aResultCode Operation result (error) code, must not be S_OK.
1055 * @param aIID IID of the interface that defines the error.
1056 * @param aComponent Name of the component that generates the error.
1057 * @param aText Error message (must not be null), an RTStrPrintf-like
1058 * format string in UTF-8 encoding.
1059 * @param va List of arguments for the format string.
1060 */
1061HRESULT Progress::i_notifyCompleteV(HRESULT aResultCode,
1062 const GUID &aIID,
1063 const char *pcszComponent,
1064 const char *aText,
1065 va_list va)
1066{
1067 Utf8Str text(aText, va);
1068
1069 AutoCaller autoCaller(this);
1070 AssertComRCReturnRC(autoCaller.rc());
1071
1072 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1073
1074 AssertReturn(mCompleted == FALSE, E_FAIL);
1075
1076 if (mCanceled && SUCCEEDED(aResultCode))
1077 aResultCode = E_FAIL;
1078
1079 mCompleted = TRUE;
1080 mResultCode = aResultCode;
1081
1082 AssertReturn(FAILED(aResultCode), E_FAIL);
1083
1084 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
1085 HRESULT rc = errorInfo.createObject();
1086 AssertComRC(rc);
1087 if (SUCCEEDED(rc))
1088 {
1089 errorInfo->init(aResultCode, aIID, pcszComponent, text);
1090 errorInfo.queryInterfaceTo(mErrorInfo.asOutParam());
1091 }
1092
1093#if !defined VBOX_COM_INPROC
1094 /* remove from the global collection of pending progress operations */
1095 if (mParent)
1096 mParent->i_removeProgress(mId.ref());
1097#endif
1098
1099 /* wake up all waiting threads */
1100 if (mWaitersCount > 0)
1101 RTSemEventMultiSignal(mCompletedSem);
1102
1103 return rc;
1104}
1105
1106/**
1107 * Notify the progress object that we're almost at the point of no return.
1108 *
1109 * This atomically checks for and disables cancelation. Calls to
1110 * IProgress::Cancel() made after a successful call to this method will fail
1111 * and the user can be told. While this isn't entirely clean behavior, it
1112 * prevents issues with an irreversible actually operation succeeding while the
1113 * user believe it was rolled back.
1114 *
1115 * @returns Success indicator.
1116 * @retval true on success.
1117 * @retval false if the progress object has already been canceled or is in an
1118 * invalid state
1119 */
1120bool Progress::i_notifyPointOfNoReturn(void)
1121{
1122 AutoCaller autoCaller(this);
1123 AssertComRCReturn(autoCaller.rc(), false);
1124
1125 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1126
1127 if (mCanceled)
1128 {
1129 LogThisFunc(("returns false\n"));
1130 return false;
1131 }
1132
1133 mCancelable = FALSE;
1134 LogThisFunc(("returns true\n"));
1135 return true;
1136}
1137
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