VirtualBox

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

Last change on this file since 96399 was 94906, checked in by vboxsync, 3 years ago

Main/ProgressImpl.cpp: Adjust to the new rules wrt. to rc -> hrc,vrc usage, bugref:10223

  • 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 94906 2022-05-07 16:21:06Z 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 hrc = unconst(pEventSource).createObject();
170 if (FAILED(hrc))
171 return hrc;
172
173 hrc = pEventSource->init();
174 if (FAILED(hrc))
175 return hrc;
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 hrc;
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 hrc = ::GetErrorInfo(0, err.asOutParam());
351 if (hrc == S_OK && err)
352 hrc = err.queryInterfaceTo(errorInfo.asOutParam());
353#else /* !defined(VBOX_WITH_XPCOM) */
354 nsCOMPtr<nsIExceptionService> es;
355 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
356 if (NS_SUCCEEDED(hrc))
357 {
358 nsCOMPtr <nsIExceptionManager> em;
359 hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
360 if (NS_SUCCEEDED(hrc))
361 {
362 ComPtr<nsIException> ex;
363 hrc = em->GetCurrentException(ex.asOutParam());
364 if (NS_SUCCEEDED(hrc) && ex)
365 hrc = 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 hrc = errorInfo.createObject();
412 AssertComRCReturnRC(hrc);
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 hrc = errorInfo.createObject();
459 AssertComRCReturnRC(hrc);
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 AssertReturn(autoCaller.isOk(), 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 BOOL fCancelable = FALSE;
927 BOOL fCompleted = FALSE;
928 BOOL fCanceled = FALSE;
929 ULONG prevPercent = UINT32_MAX;
930 ULONG currentPercent = 0;
931 ULONG cOp = 0;
932 /* Is the async process cancelable? */
933 HRESULT hrc = aProgressOther->COMGETTER(Cancelable)(&fCancelable);
934 if (FAILED(hrc)) return hrc;
935
936 uint64_t u64StopTime = UINT64_MAX;
937 if (aTimeoutMS > 0)
938 u64StopTime = RTTimeMilliTS() + aTimeoutMS;
939 /* Loop as long as the sync process isn't completed. */
940 while (SUCCEEDED(aProgressOther->COMGETTER(Completed(&fCompleted))))
941 {
942 /* We can forward any cancel request to the async process only when
943 * it is cancelable. */
944 if (fCancelable)
945 {
946 hrc = COMGETTER(Canceled)(&fCanceled);
947 if (FAILED(hrc)) return hrc;
948 if (fCanceled)
949 {
950 hrc = aProgressOther->Cancel();
951 if (FAILED(hrc)) return hrc;
952 }
953 }
954 /* Even if the user canceled the process, we have to wait until the
955 async task has finished his work (cleanup and such). Otherwise there
956 will be sync trouble (still wrong state, dead locks, ...) on the
957 used objects. So just do nothing, but wait for the complete
958 notification. */
959 if (!fCanceled)
960 {
961 /* Check if the current operation has changed. It is also possible that
962 * in the meantime more than one async operation was finished. So we
963 * have to loop as long as we reached the same operation count. */
964 ULONG curOp;
965 for (;;)
966 {
967 hrc = aProgressOther->COMGETTER(Operation(&curOp));
968 if (FAILED(hrc)) return hrc;
969 if (cOp != curOp)
970 {
971 Bstr bstr;
972 ULONG currentWeight;
973 hrc = aProgressOther->COMGETTER(OperationDescription(bstr.asOutParam()));
974 if (FAILED(hrc)) return hrc;
975 hrc = aProgressOther->COMGETTER(OperationWeight(&currentWeight));
976 if (FAILED(hrc)) return hrc;
977 hrc = SetNextOperation(bstr.raw(), currentWeight);
978 if (FAILED(hrc)) return hrc;
979 ++cOp;
980 }
981 else
982 break;
983 }
984
985 hrc = aProgressOther->COMGETTER(OperationPercent(&currentPercent));
986 if (FAILED(hrc)) return hrc;
987 if (currentPercent != prevPercent)
988 {
989 prevPercent = currentPercent;
990 hrc = SetCurrentOperationProgress(currentPercent);
991 if (FAILED(hrc)) return hrc;
992 }
993 }
994 if (fCompleted)
995 break;
996
997 if (aTimeoutMS != 0)
998 {
999 /* Make sure the loop is not too tight */
1000 uint64_t u64Now = RTTimeMilliTS();
1001 uint64_t u64RemainingMS = u64StopTime - u64Now;
1002 if (u64RemainingMS < 10)
1003 u64RemainingMS = 10;
1004 else if (u64RemainingMS > 200)
1005 u64RemainingMS = 200;
1006 hrc = aProgressOther->WaitForCompletion((LONG)u64RemainingMS);
1007 if (FAILED(hrc)) return hrc;
1008
1009 if (RTTimeMilliTS() >= u64StopTime)
1010 return VBOX_E_TIMEOUT;
1011 }
1012 else
1013 {
1014 /* Make sure the loop is not too tight */
1015 hrc = aProgressOther->WaitForCompletion(200);
1016 if (FAILED(hrc)) return hrc;
1017 }
1018 }
1019
1020 /* Transfer error information if applicable and report the error status
1021 * back to the caller to make this as easy as possible. */
1022 LONG iRc;
1023 hrc = aProgressOther->COMGETTER(ResultCode)(&iRc);
1024 if (FAILED(hrc)) return hrc;
1025 if (FAILED((HRESULT)iRc))
1026 {
1027 setError(ProgressErrorInfo(aProgressOther));
1028 hrc = (HRESULT)iRc;
1029 }
1030
1031 LogFlowThisFuncLeave();
1032 return hrc;
1033}
1034
1035/**
1036 * Signals that the current operation is successfully completed and advances to
1037 * the next operation. The operation percentage is reset to 0.
1038 *
1039 * @param aNextOperationDescription Description of the next operation.
1040 * @param aNextOperationsWeight Weight of the next operation.
1041 *
1042 * @note The current operation must not be the last one.
1043 */
1044HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
1045{
1046 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1047
1048 if (mCanceled)
1049 return E_FAIL;
1050 AssertReturn(!mCompleted, E_FAIL);
1051 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
1052
1053 ++m_ulCurrentOperation;
1054 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
1055
1056 m_operationDescription = aNextOperationDescription;
1057 m_ulCurrentOperationWeight = aNextOperationsWeight;
1058 m_ulOperationPercent = 0;
1059
1060 LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1061 m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1062
1063 /* wake up all waiting threads */
1064 if (mWaitersCount > 0)
1065 RTSemEventMultiSignal(mCompletedSem);
1066
1067 ULONG actualPercent = 0;
1068 getPercent(&actualPercent);
1069 ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
1070
1071 return S_OK;
1072}
1073
1074/**
1075 * Notify the progress object that we're almost at the point of no return.
1076 *
1077 * This atomically checks for and disables cancelation. Calls to
1078 * IProgress::Cancel() made after a successful call to this method will fail
1079 * and the user can be told. While this isn't entirely clean behavior, it
1080 * prevents issues with an irreversible actually operation succeeding while the
1081 * user believe it was rolled back.
1082 *
1083 * @returns COM error status.
1084 * @retval S_OK on success.
1085 * @retval E_FAIL if the progress object has already been canceled or is in an
1086 * invalid state
1087 */
1088HRESULT Progress::notifyPointOfNoReturn(void)
1089{
1090 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1091
1092 if (mCanceled)
1093 {
1094 LogThisFunc(("returns failure\n"));
1095 return E_FAIL;
1096 }
1097
1098 mCancelable = FALSE;
1099 LogThisFunc(("returns success\n"));
1100 return S_OK;
1101}
1102
1103/**
1104 * Marks the operation as complete and attaches full error info.
1105 *
1106 * This is where the actual work is done, the related methods all end up here.
1107 *
1108 * @param aResultCode Operation result (error) code, must not be S_OK.
1109 * @param aErrorInfo List of arguments for the format string.
1110 */
1111HRESULT Progress::notifyComplete(LONG aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
1112{
1113 return i_notifyCompleteWorker((HRESULT)aResultCode, aErrorInfo);
1114}
1115
1116
1117// private internal helpers
1118/////////////////////////////////////////////////////////////////////////////
1119
1120/**
1121 * Marks the operation as complete and attaches full error info.
1122 *
1123 * This is where the actual work is done, the related methods all end up here.
1124 *
1125 * @param aResultCode Operation result (error) code, must not be S_OK.
1126 * @param aErrorInfo List of arguments for the format string.
1127 *
1128 * @note This is just notifyComplete with the correct aResultCode type.
1129 */
1130HRESULT Progress::i_notifyCompleteWorker(HRESULT aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
1131{
1132 LogThisFunc(("aResultCode=%Rhrc\n", aResultCode));
1133 /* on failure we expect error info, on success there must be none */
1134 AssertMsg(FAILED(aResultCode) ^ aErrorInfo.isNull(),
1135 ("No error info but trying to set a failed result (%08X/%Rhrc)!\n", aResultCode, aResultCode));
1136
1137 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1138
1139 AssertReturn(mCompleted == FALSE, E_FAIL);
1140
1141 if (mCanceled && SUCCEEDED(aResultCode))
1142 aResultCode = E_FAIL;
1143
1144 mCompleted = TRUE;
1145 mResultCode = aResultCode;
1146 if (SUCCEEDED(aResultCode))
1147 {
1148 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1149 m_ulOperationPercent = 100;
1150 }
1151 mErrorInfo = aErrorInfo;
1152
1153#if !defined(VBOX_COM_INPROC)
1154 /* remove from the global collection of pending progress operations */
1155 if (mParent)
1156 mParent->i_removeProgress(mId.ref());
1157#endif
1158
1159 /* wake up all waiting threads */
1160 if (mWaitersCount > 0)
1161 RTSemEventMultiSignal(mCompletedSem);
1162
1163 ::FireProgressTaskCompletedEvent(pEventSource, mId.toString());
1164
1165 return S_OK;
1166}
1167
1168/**
1169 * Internal helper to compute the total percent value based on the member values and
1170 * returns it as a "double". This is used both by GetPercent (which returns it as a
1171 * rounded ULONG) and GetTimeRemaining().
1172 *
1173 * Requires locking by the caller!
1174 *
1175 * @return fractional percentage as a double value.
1176 */
1177double Progress::i_calcTotalPercent()
1178{
1179 // avoid division by zero
1180 if (m_ulTotalOperationsWeight == 0)
1181 return 0.0;
1182
1183 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
1184 + ((double)m_ulOperationPercent *
1185 (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
1186 ) * 100.0 / (double)m_ulTotalOperationsWeight;
1187
1188 return dPercent;
1189}
1190
1191/**
1192 * Internal helper for automatically timing out the operation.
1193 *
1194 * The caller must hold the object write lock.
1195 */
1196void Progress::i_checkForAutomaticTimeout(void)
1197{
1198 AssertReturnVoid(isWriteLockOnCurrentThread());
1199
1200 if ( m_cMsTimeout
1201 && mCancelable
1202 && !mCanceled
1203 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
1204 Cancel();
1205}
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