VirtualBox

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

Last change on this file since 51193 was 51092, checked in by vboxsync, 11 years ago

6813 src-client/MachineDebuggerImpl.cpp + various formatting changes

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

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