VirtualBox

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

Last change on this file since 107437 was 106976, checked in by vboxsync, 2 months ago

Added tracking for medium, machine, session, progress objects.

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