VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use