VirtualBox

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

Last change on this file since 94267 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.2 KB
Line 
1/* $Id: ProgressImpl.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * VirtualBox Progress COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2022 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 i_notifyCompleteWorker(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 i_notifyCompleteWorker(aResultCode, errorInfo);
416}
417
418/**
419 * Wrapper around Progress:notifyCompleteBothV.
420 */
421HRESULT Progress::i_notifyCompleteBoth(HRESULT aResultCode,
422 int vrc,
423 const GUID &aIID,
424 const char *pcszComponent,
425 const char *aText,
426 ...)
427{
428 va_list va;
429 va_start(va, aText);
430 HRESULT hrc = i_notifyCompleteBothV(aResultCode, vrc, aIID, pcszComponent, aText, va);
431 va_end(va);
432 return hrc;
433}
434
435/**
436 * Marks the operation as complete and attaches full error info.
437 *
438 * @param aResultCode Operation result (error) code, must not be S_OK.
439 * @param vrc VBox status code to associate with the error.
440 * @param aIID IID of the interface that defines the error.
441 * @param pszComponent Name of the component that generates the error.
442 * @param pszFormat Error message (must not be null), an RTStrPrintf-like
443 * format string in UTF-8 encoding.
444 * @param va List of arguments for the format string.
445 */
446HRESULT Progress::i_notifyCompleteBothV(HRESULT aResultCode,
447 int vrc,
448 const GUID &aIID,
449 const char *pszComponent,
450 const char *pszFormat,
451 va_list va)
452{
453 /* expected to be used only in case of error */
454 Assert(FAILED(aResultCode));
455
456 Utf8Str text(pszFormat, va);
457 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
458 HRESULT rc = errorInfo.createObject();
459 AssertComRCReturnRC(rc);
460 errorInfo->initEx(aResultCode, vrc, aIID, pszComponent, text);
461
462 return i_notifyCompleteWorker(aResultCode, errorInfo);
463}
464
465/**
466 * Sets the cancelation callback, checking for cancelation first.
467 *
468 * @returns Success indicator.
469 * @retval true on success.
470 * @retval false if the progress object has already been canceled or is in an
471 * invalid state
472 *
473 * @param pfnCallback The function to be called upon cancelation.
474 * @param pvUser The callback argument.
475 */
476bool Progress::i_setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
477{
478 AutoCaller autoCaller(this);
479 AssertComRCReturn(autoCaller.rc(), false);
480
481 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
482
483 i_checkForAutomaticTimeout();
484 if (mCanceled)
485 return false;
486
487 m_pvCancelUserArg = pvUser;
488 m_pfnCancelCallback = pfnCallback;
489 return true;
490}
491
492/**
493 * @callback_method_impl{FNRTPROGRESS,
494 * Works the progress of the current operation.}
495 */
496/*static*/ DECLCALLBACK(int) Progress::i_iprtProgressCallback(unsigned uPercentage, void *pvUser)
497{
498 Progress *pThis = (Progress *)pvUser;
499
500 /*
501 * Same as setCurrentOperationProgress, except we don't fail on mCompleted.
502 */
503 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
504 int vrc = VINF_SUCCESS;
505 if (!pThis->mCompleted)
506 {
507 pThis->i_checkForAutomaticTimeout();
508 if (!pThis->mCanceled)
509 {
510 if (uPercentage > pThis->m_ulOperationPercent)
511 pThis->setCurrentOperationProgress(uPercentage);
512 }
513 else
514 {
515 Assert(pThis->mCancelable);
516 vrc = VERR_CANCELLED;
517 }
518 }
519 /* else ignored */
520 return vrc;
521}
522
523/**
524 * @callback_method_impl{FNVDPROGRESS,
525 * Progress::i_iprtProgressCallback with parameters switched around.}
526 */
527/*static*/ DECLCALLBACK(int) Progress::i_vdProgressCallback(void *pvUser, unsigned uPercentage)
528{
529 return i_iprtProgressCallback(uPercentage, pvUser);
530}
531
532
533// IProgress properties
534/////////////////////////////////////////////////////////////////////////////
535
536HRESULT Progress::getId(com::Guid &aId)
537{
538 /* mId is constant during life time, no need to lock */
539 aId = mId;
540
541 return S_OK;
542}
543
544HRESULT Progress::getDescription(com::Utf8Str &aDescription)
545{
546 /* mDescription is constant during life time, no need to lock */
547 aDescription = mDescription;
548
549 return S_OK;
550}
551HRESULT Progress::getInitiator(ComPtr<IUnknown> &aInitiator)
552{
553 /* mInitiator/mParent are constant during life time, no need to lock */
554#if !defined(VBOX_COM_INPROC)
555 if (mInitiator)
556 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
557 else
558 {
559 ComObjPtr<VirtualBox> pVirtualBox(mParent);
560 pVirtualBox.queryInterfaceTo(aInitiator.asOutParam());
561 }
562#else
563 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
564#endif
565
566 return S_OK;
567}
568
569HRESULT Progress::getCancelable(BOOL *aCancelable)
570{
571 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
572
573 *aCancelable = mCancelable;
574
575 return S_OK;
576}
577
578HRESULT Progress::getPercent(ULONG *aPercent)
579{
580 /* i_checkForAutomaticTimeout requires a write lock. */
581 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
582
583 if (mCompleted && SUCCEEDED(mResultCode))
584 *aPercent = 100;
585 else
586 {
587 ULONG ulPercent = (ULONG)i_calcTotalPercent();
588 // do not report 100% until we're really really done with everything
589 // as the Qt GUI dismisses progress dialogs in that case
590 if ( ulPercent == 100
591 && ( m_ulOperationPercent < 100
592 || (m_ulCurrentOperation < m_cOperations -1)
593 )
594 )
595 *aPercent = 99;
596 else
597 *aPercent = ulPercent;
598 }
599
600 i_checkForAutomaticTimeout();
601
602 return S_OK;
603}
604
605HRESULT Progress::getTimeRemaining(LONG *aTimeRemaining)
606{
607 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
608
609 if (mCompleted)
610 *aTimeRemaining = 0;
611 else
612 {
613 double dPercentDone = i_calcTotalPercent();
614 if (dPercentDone < 1)
615 *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
616 else
617 {
618 uint64_t ullTimeNow = RTTimeMilliTS();
619 uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
620 uint64_t ullTimeTotal = (uint64_t)((double)ullTimeElapsed * 100 / dPercentDone);
621 uint64_t ullTimeRemaining = (ullTimeTotal < ullTimeElapsed) ? 0 : ullTimeTotal - ullTimeElapsed;
622
623// LogFunc(("dPercentDone = %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
624// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
625
626 *aTimeRemaining = (LONG)(RT_MIN(ullTimeRemaining, RT_MS_1HOUR_64*24*365) / 1000);
627 }
628 }
629
630 return S_OK;
631}
632
633HRESULT Progress::getCompleted(BOOL *aCompleted)
634{
635 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
636
637 *aCompleted = mCompleted;
638
639 return S_OK;
640}
641
642HRESULT Progress::getCanceled(BOOL *aCanceled)
643{
644 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
645
646 *aCanceled = mCanceled;
647
648 return S_OK;
649}
650
651HRESULT Progress::getResultCode(LONG *aResultCode)
652{
653 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
654
655 if (!mCompleted)
656 return setError(E_FAIL, tr("Result code is not available, operation is still in progress"));
657
658 *aResultCode = (LONG)mResultCode;
659
660 return S_OK;
661}
662
663HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
664{
665 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
666
667 if (!mCompleted)
668 return setError(E_FAIL, tr("Error info is not available, operation is still in progress"));
669
670 mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
671
672 return S_OK;
673}
674
675HRESULT Progress::getOperationCount(ULONG *aOperationCount)
676{
677 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
678
679 *aOperationCount = m_cOperations;
680
681 return S_OK;
682}
683
684HRESULT Progress::getOperation(ULONG *aOperation)
685{
686 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
687
688 *aOperation = m_ulCurrentOperation;
689
690 return S_OK;
691}
692
693HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
694{
695 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
696
697 aOperationDescription = m_operationDescription;
698
699 return S_OK;
700}
701
702HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
703{
704 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
705
706 if (mCompleted && SUCCEEDED(mResultCode))
707 *aOperationPercent = 100;
708 else
709 *aOperationPercent = m_ulOperationPercent;
710
711 return S_OK;
712}
713
714HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
715{
716 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
717
718 *aOperationWeight = m_ulCurrentOperationWeight;
719
720 return S_OK;
721}
722
723HRESULT Progress::getTimeout(ULONG *aTimeout)
724{
725 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
726
727 *aTimeout = m_cMsTimeout;
728
729 return S_OK;
730}
731
732HRESULT Progress::setTimeout(ULONG aTimeout)
733{
734 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
735
736 if (!mCancelable)
737 return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
738 m_cMsTimeout = aTimeout;
739
740 return S_OK;
741}
742
743HRESULT Progress::getEventSource(ComPtr<IEventSource> &aEventSource)
744{
745 /* event source is const, no need to lock */
746 pEventSource.queryInterfaceTo(aEventSource.asOutParam());
747 return S_OK;
748}
749
750
751// IProgress methods
752/////////////////////////////////////////////////////////////////////////////
753
754/**
755 * @note XPCOM: when this method is not called on the main XPCOM thread, it
756 * simply blocks the thread until mCompletedSem is signalled. If the
757 * thread has its own event queue (hmm, what for?) that it must run, then
758 * calling this method will definitely freeze event processing.
759 */
760HRESULT Progress::waitForCompletion(LONG aTimeout)
761{
762 LogFlowThisFuncEnter();
763 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
764
765 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
766
767 /* if we're already completed, take a shortcut */
768 if (!mCompleted && aTimeout != 0)
769 {
770 RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
771 uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
772
773 for (;;)
774 {
775 mWaitersCount++;
776 alock.release();
777 int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
778 alock.acquire();
779 mWaitersCount--;
780
781 /* the last waiter resets the semaphore */
782 if (mWaitersCount == 0)
783 RTSemEventMultiReset(mCompletedSem);
784
785 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
786 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to wait for the task completion (%Rrc)"), vrc);
787
788 if (mCompleted)
789 break;
790
791 if (aTimeout >= 0)
792 {
793 uint64_t msNow = RTTimeMilliTS();
794 uint64_t cMsElapsed = msNow - msLast;
795 if (cMsWait <= cMsElapsed)
796 break;
797 cMsWait -= (RTMSINTERVAL)cMsElapsed;
798 msLast = msNow;
799 }
800 }
801 }
802
803 LogFlowThisFuncLeave();
804 return S_OK;
805}
806
807/**
808 * @note XPCOM: when this method is not called on the main XPCOM thread, it
809 * simply blocks the thread until mCompletedSem is signalled. If the
810 * thread has its own event queue (hmm, what for?) that it must run, then
811 * calling this method will definitely freeze event processing.
812 */
813HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
814
815{
816 LogFlowThisFuncEnter();
817 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
818
819 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
820
821 CheckComArgExpr(aOperation, aOperation < m_cOperations);
822
823 /* if we're already completed or if the given operation is already done,
824 * then take a shortcut */
825 if ( !mCompleted
826 && aOperation >= m_ulCurrentOperation
827 && aTimeout != 0)
828 {
829 RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
830 uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
831
832 for (;;)
833 {
834 mWaitersCount ++;
835 alock.release();
836 int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
837 alock.acquire();
838 mWaitersCount--;
839
840 /* the last waiter resets the semaphore */
841 if (mWaitersCount == 0)
842 RTSemEventMultiReset(mCompletedSem);
843
844 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
845 return setErrorBoth(E_FAIL, vrc, tr("Failed to wait for the operation completion (%Rrc)"), vrc);
846
847 if (mCompleted || aOperation >= m_ulCurrentOperation)
848 break;
849
850 if (aTimeout >= 0)
851 {
852 uint64_t msNow = RTTimeMilliTS();
853 uint64_t cMsElapsed = msNow - msLast;
854 if (cMsWait <= cMsElapsed)
855 break;
856 cMsWait -= (RTMSINTERVAL)cMsElapsed;
857 msLast = msNow;
858 }
859 }
860 }
861
862 LogFlowThisFuncLeave();
863 return S_OK;
864}
865
866HRESULT Progress::cancel()
867{
868 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
869
870 if (!mCancelable)
871 return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
872
873 if (!mCanceled)
874 {
875 LogThisFunc(("Canceling\n"));
876 mCanceled = TRUE;
877 if (m_pfnCancelCallback)
878 m_pfnCancelCallback(m_pvCancelUserArg);
879
880 }
881 else
882 LogThisFunc(("Already canceled\n"));
883
884 return S_OK;
885}
886
887
888// IInternalProgressControl methods
889/////////////////////////////////////////////////////////////////////////////
890
891/**
892 * Updates the percentage value of the current operation.
893 *
894 * @param aPercent New percentage value of the operation in progress
895 * (in range [0, 100]).
896 */
897HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
898{
899 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
900
901 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
902
903 i_checkForAutomaticTimeout();
904 if (mCancelable && mCanceled)
905 AssertReturn(!mCompleted, E_FAIL);
906 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
907
908 if (m_ulOperationPercent != aPercent)
909 {
910 m_ulOperationPercent = aPercent;
911 ULONG actualPercent = 0;
912 getPercent(&actualPercent);
913 ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
914 }
915
916 return S_OK;
917}
918
919HRESULT Progress::waitForOtherProgressCompletion(const ComPtr<IProgress> &aProgressOther,
920 ULONG aTimeoutMS)
921{
922 LogFlowThisFuncEnter();
923
924 /* Note: no locking needed, because we just use public methods. */
925
926 HRESULT rc = S_OK;
927 BOOL fCancelable = FALSE;
928 BOOL fCompleted = FALSE;
929 BOOL fCanceled = FALSE;
930 ULONG prevPercent = UINT32_MAX;
931 ULONG currentPercent = 0;
932 ULONG cOp = 0;
933 /* Is the async process cancelable? */
934 rc = aProgressOther->COMGETTER(Cancelable)(&fCancelable);
935 if (FAILED(rc)) return rc;
936
937 uint64_t u64StopTime = UINT64_MAX;
938 if (aTimeoutMS > 0)
939 u64StopTime = RTTimeMilliTS() + aTimeoutMS;
940 /* Loop as long as the sync process isn't completed. */
941 while (SUCCEEDED(aProgressOther->COMGETTER(Completed(&fCompleted))))
942 {
943 /* We can forward any cancel request to the async process only when
944 * it is cancelable. */
945 if (fCancelable)
946 {
947 rc = COMGETTER(Canceled)(&fCanceled);
948 if (FAILED(rc)) return rc;
949 if (fCanceled)
950 {
951 rc = aProgressOther->Cancel();
952 if (FAILED(rc)) return rc;
953 }
954 }
955 /* Even if the user canceled the process, we have to wait until the
956 async task has finished his work (cleanup and such). Otherwise there
957 will be sync trouble (still wrong state, dead locks, ...) on the
958 used objects. So just do nothing, but wait for the complete
959 notification. */
960 if (!fCanceled)
961 {
962 /* Check if the current operation has changed. It is also possible that
963 * in the meantime more than one async operation was finished. So we
964 * have to loop as long as we reached the same operation count. */
965 ULONG curOp;
966 for (;;)
967 {
968 rc = aProgressOther->COMGETTER(Operation(&curOp));
969 if (FAILED(rc)) return rc;
970 if (cOp != curOp)
971 {
972 Bstr bstr;
973 ULONG currentWeight;
974 rc = aProgressOther->COMGETTER(OperationDescription(bstr.asOutParam()));
975 if (FAILED(rc)) return rc;
976 rc = aProgressOther->COMGETTER(OperationWeight(&currentWeight));
977 if (FAILED(rc)) return rc;
978 rc = SetNextOperation(bstr.raw(), currentWeight);
979 if (FAILED(rc)) return rc;
980 ++cOp;
981 }
982 else
983 break;
984 }
985
986 rc = aProgressOther->COMGETTER(OperationPercent(&currentPercent));
987 if (FAILED(rc)) return rc;
988 if (currentPercent != prevPercent)
989 {
990 prevPercent = currentPercent;
991 rc = SetCurrentOperationProgress(currentPercent);
992 if (FAILED(rc)) return rc;
993 }
994 }
995 if (fCompleted)
996 break;
997
998 if (aTimeoutMS != 0)
999 {
1000 /* Make sure the loop is not too tight */
1001 uint64_t u64Now = RTTimeMilliTS();
1002 uint64_t u64RemainingMS = u64StopTime - u64Now;
1003 if (u64RemainingMS < 10)
1004 u64RemainingMS = 10;
1005 else if (u64RemainingMS > 200)
1006 u64RemainingMS = 200;
1007 rc = aProgressOther->WaitForCompletion((LONG)u64RemainingMS);
1008 if (FAILED(rc)) return rc;
1009
1010 if (RTTimeMilliTS() >= u64StopTime)
1011 return VBOX_E_TIMEOUT;
1012 }
1013 else
1014 {
1015 /* Make sure the loop is not too tight */
1016 rc = aProgressOther->WaitForCompletion(200);
1017 if (FAILED(rc)) return rc;
1018 }
1019 }
1020
1021 /* Transfer error information if applicable and report the error status
1022 * back to the caller to make this as easy as possible. */
1023 LONG iRc;
1024 rc = aProgressOther->COMGETTER(ResultCode)(&iRc);
1025 if (FAILED(rc)) return rc;
1026 if (FAILED((HRESULT)iRc))
1027 {
1028 setError(ProgressErrorInfo(aProgressOther));
1029 rc = (HRESULT)iRc;
1030 }
1031
1032 LogFlowThisFuncLeave();
1033 return rc;
1034}
1035
1036/**
1037 * Signals that the current operation is successfully completed and advances to
1038 * the next operation. The operation percentage is reset to 0.
1039 *
1040 * @param aNextOperationDescription Description of the next operation.
1041 * @param aNextOperationsWeight Weight of the next operation.
1042 *
1043 * @note The current operation must not be the last one.
1044 */
1045HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
1046{
1047 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1048
1049 if (mCanceled)
1050 return E_FAIL;
1051 AssertReturn(!mCompleted, E_FAIL);
1052 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
1053
1054 ++m_ulCurrentOperation;
1055 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
1056
1057 m_operationDescription = aNextOperationDescription;
1058 m_ulCurrentOperationWeight = aNextOperationsWeight;
1059 m_ulOperationPercent = 0;
1060
1061 LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1062 m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1063
1064 /* wake up all waiting threads */
1065 if (mWaitersCount > 0)
1066 RTSemEventMultiSignal(mCompletedSem);
1067
1068 ULONG actualPercent = 0;
1069 getPercent(&actualPercent);
1070 ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
1071
1072 return S_OK;
1073}
1074
1075/**
1076 * Notify the progress object that we're almost at the point of no return.
1077 *
1078 * This atomically checks for and disables cancelation. Calls to
1079 * IProgress::Cancel() made after a successful call to this method will fail
1080 * and the user can be told. While this isn't entirely clean behavior, it
1081 * prevents issues with an irreversible actually operation succeeding while the
1082 * user believe it was rolled back.
1083 *
1084 * @returns COM error status.
1085 * @retval S_OK on success.
1086 * @retval E_FAIL if the progress object has already been canceled or is in an
1087 * invalid state
1088 */
1089HRESULT Progress::notifyPointOfNoReturn(void)
1090{
1091 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1092
1093 if (mCanceled)
1094 {
1095 LogThisFunc(("returns failure\n"));
1096 return E_FAIL;
1097 }
1098
1099 mCancelable = FALSE;
1100 LogThisFunc(("returns success\n"));
1101 return S_OK;
1102}
1103
1104/**
1105 * Marks the operation as complete and attaches full error info.
1106 *
1107 * This is where the actual work is done, the related methods all end up here.
1108 *
1109 * @param aResultCode Operation result (error) code, must not be S_OK.
1110 * @param aErrorInfo List of arguments for the format string.
1111 */
1112HRESULT Progress::notifyComplete(LONG aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
1113{
1114 return i_notifyCompleteWorker((HRESULT)aResultCode, aErrorInfo);
1115}
1116
1117
1118// private internal helpers
1119/////////////////////////////////////////////////////////////////////////////
1120
1121/**
1122 * Marks the operation as complete and attaches full error info.
1123 *
1124 * This is where the actual work is done, the related methods all end up here.
1125 *
1126 * @param aResultCode Operation result (error) code, must not be S_OK.
1127 * @param aErrorInfo List of arguments for the format string.
1128 *
1129 * @note This is just notifyComplete with the correct aResultCode type.
1130 */
1131HRESULT Progress::i_notifyCompleteWorker(HRESULT aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
1132{
1133 LogThisFunc(("aResultCode=%Rhrc\n", aResultCode));
1134 /* on failure we expect error info, on success there must be none */
1135 AssertMsg(FAILED(aResultCode) ^ aErrorInfo.isNull(),
1136 ("No error info but trying to set a failed result (%08X/%Rhrc)!\n", aResultCode, aResultCode));
1137
1138 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1139
1140 AssertReturn(mCompleted == FALSE, E_FAIL);
1141
1142 if (mCanceled && SUCCEEDED(aResultCode))
1143 aResultCode = E_FAIL;
1144
1145 mCompleted = TRUE;
1146 mResultCode = aResultCode;
1147 if (SUCCEEDED(aResultCode))
1148 {
1149 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1150 m_ulOperationPercent = 100;
1151 }
1152 mErrorInfo = aErrorInfo;
1153
1154#if !defined(VBOX_COM_INPROC)
1155 /* remove from the global collection of pending progress operations */
1156 if (mParent)
1157 mParent->i_removeProgress(mId.ref());
1158#endif
1159
1160 /* wake up all waiting threads */
1161 if (mWaitersCount > 0)
1162 RTSemEventMultiSignal(mCompletedSem);
1163
1164 ::FireProgressTaskCompletedEvent(pEventSource, mId.toString());
1165
1166 return S_OK;
1167}
1168
1169/**
1170 * Internal helper to compute the total percent value based on the member values and
1171 * returns it as a "double". This is used both by GetPercent (which returns it as a
1172 * rounded ULONG) and GetTimeRemaining().
1173 *
1174 * Requires locking by the caller!
1175 *
1176 * @return fractional percentage as a double value.
1177 */
1178double Progress::i_calcTotalPercent()
1179{
1180 // avoid division by zero
1181 if (m_ulTotalOperationsWeight == 0)
1182 return 0.0;
1183
1184 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
1185 + ((double)m_ulOperationPercent *
1186 (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
1187 ) * 100.0 / (double)m_ulTotalOperationsWeight;
1188
1189 return dPercent;
1190}
1191
1192/**
1193 * Internal helper for automatically timing out the operation.
1194 *
1195 * The caller must hold the object write lock.
1196 */
1197void Progress::i_checkForAutomaticTimeout(void)
1198{
1199 AssertReturnVoid(isWriteLockOnCurrentThread());
1200
1201 if ( m_cMsTimeout
1202 && mCancelable
1203 && !mCanceled
1204 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
1205 Cancel();
1206}
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