VirtualBox

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

Last change on this file since 78018 was 77252, checked in by vboxsync, 6 years ago

bugref:9349. Fixed aTimeRemaining wrong calculation.

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