VirtualBox

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

Last change on this file since 76319 was 74804, checked in by vboxsync, 6 years ago

Main/Progress: Split into safe public interface and a private one which is used purely by API implementation code (for updating). Needed quite a few minor adjustments elsewhere.

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