1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 action::{Action, ServiceAction},
8 ErrorStage::{MainThread, Pretask},
11 BitsStateCancelled, FailedToDispatchRunnable, FailedToStartThread, InvalidArgument,
12 OperationAlreadyInProgress, TransferAlreadyComplete,
15 monitor::MonitorRunnable,
17 CancelTask, ChangeMonitorIntervalTask, CompleteTask, Priority, ResumeTask,
18 SetNoProgressTimeoutTask, SetPriorityTask, SuspendTask,
20 BitsService, BitsTaskError,
22 use nsIBitsRequest_method; // From xpcom_method.rs
24 use bits_client::{BitsMonitorClient, Guid};
25 use log::{error, info, warn};
26 use moz_task::create_thread;
27 use nserror::{nsresult, NS_ERROR_ABORT, NS_ERROR_NOT_IMPLEMENTED, NS_OK};
28 use nsstring::{nsACString, nsCString};
29 use std::{cell::Cell, fmt};
32 nsIBits, nsIBitsCallback, nsILoadGroup, nsIProgressEventSink, nsIRequestObserver,
33 nsISupports, nsIThread, nsLoadFlags,
35 xpcom, xpcom_method, RefPtr, XpCom,
38 /// This structure exists to resolve a race condition. If cancel is called, we
39 /// don't want to immediately set the request state to cancelled, because the
40 /// cancel action could fail. But it's possible that on_stop() could be called
41 /// before the cancel action resolves, and the correct status should be sent to
43 /// This is how this race condition will be resolved:
44 /// 1. cancel() is called, which sets the CancelAction to InProgress and
45 /// stores in it the status that should be set if it succeeds.
46 /// 2. cancel() dispatches the cancel task off thread.
47 /// At this point, things unfold in one of two ways, depending on the race
48 /// condition. Either:
49 /// 3. The cancel task returns to the main thread and calls
50 /// BitsRequest::finish_cancel_action.
51 /// 4. If the cancel action succeeded, the appropriate status codes are set
52 /// and the CancelAction is set to RequestEndPending.
53 /// If the cancel action failed, the CancelAction is set to NotInProgress.
54 /// 5. The MonitorRunnable detects that the transfer has ended and calls
55 /// BitsRequest::on_stop, passing different status codes.
56 /// 6. BitsRequest::on_stop checks the CancelAction and
57 /// If the cancel action succeeded and RequestEndPending is set, the
58 /// status codes that were set by BitsRequest::finish_cancel_action are
60 /// If the cancel action failed and NotInProgress is set, the status codes
61 /// passed to BitsRequest::on_stop are set.
62 /// 7. onStopRequest is called with the correct status code.
63 /// Or, if MonitorRunnable calls on_stop before the cancel task can finish:
64 /// 3. The MonitorRunnable detects that the transfer has ended and calls
65 /// BitsRequest::on_stop, passing status codes to it.
66 /// 4. BitsRequest::on_stop checks the CancelAction, sees it is set to
67 /// InProgress, and sets it to RequestEndedWhileInProgress, carrying over
68 /// the status code from InProgress.
69 /// 5. BitsRequest::on_stop sets the status to the value passed to it, which
70 /// will be overwritten if the cancel action succeeds, but kept if it
72 /// 6. BitsRequest::on_stop returns early, without calling OnStopRequest.
73 /// 7. The cancel task returns to the main thread and calls
74 /// BitsRequest::finish_cancel_action.
75 /// 8. If the cancel action succeeded, the status codes are set from the
76 /// value stored in RequestEndedWhileInProgress.
77 /// If the cancel action failed, the status codes are not changed.
78 /// 9. The CancelAction is set to NotInProgress.
79 /// 10. BitsRequest::finish_cancel_action calls BitsRequest::on_stop without
80 /// passing it any status codes.
81 /// 11. onStopRequest is called with the correct status code.
82 #[derive(Clone, Copy, PartialEq)]
85 InProgress(Option<nsresult>),
86 RequestEndedWhileInProgress(Option<nsresult>),
90 #[xpcom(implement(nsIBitsRequest), nonatomic)]
91 pub struct BitsRequest {
93 bits_service: RefPtr<BitsService>,
94 // Stores the value to be returned by nsIRequest::IsPending.
95 download_pending: Cell<bool>,
96 // Stores the value to be returned by nsIRequest::GetStatus.
97 download_status_nsresult: Cell<nsresult>,
98 // Stores an ErrorType if the request has failed, or None to represent the
100 download_status_error_type: Cell<Option<ErrorType>>,
101 // This option will be None only after OnStopRequest has been fired.
102 monitor_thread: Cell<Option<RefPtr<nsIThread>>>,
103 monitor_timeout_ms: u32,
104 observer: RefPtr<nsIRequestObserver>,
105 // started indicates whether or not OnStartRequest has been fired.
107 // finished indicates whether or not we have called
108 // BitsService::dec_request_count() to (assuming that there are no other
109 // requests) shutdown the command thread.
110 finished: Cell<bool>,
111 cancel_action: Cell<CancelAction>,
114 impl fmt::Debug for BitsRequest {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 write!(f, "BitsRequest {{ id: {} }}", self.bits_id)
120 /// This implements the nsIBitsRequest interface, documented in nsIBits.idl, to
121 /// enable BITS job management. This interface deals only with BITS jobs that
122 /// already exist. Jobs can be created via BitsService, which will create a
123 /// BitsRequest for that job.
125 /// This is a primarily asynchronous interface, which is accomplished via
126 /// callbacks of type nsIBitsCallback. The callback is passed in as an argument
127 /// and is then passed off-thread via a Task. The Task interacts with BITS and
128 /// is dispatched back to the main thread with the BITS result. Back on the main
129 /// thread, it returns that result via the callback.
133 bits_service: RefPtr<BitsService>,
134 monitor_timeout_ms: u32,
135 observer: RefPtr<nsIRequestObserver>,
136 context: Option<RefPtr<nsISupports>>,
137 monitor_client: BitsMonitorClient,
138 action: ServiceAction,
139 ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
141 let action: Action = action.into();
142 let monitor_thread = create_thread("BitsMonitor").map_err(|rv| {
143 BitsTaskError::from_nsresult(FailedToStartThread, action, MainThread, rv)
146 // BitsRequest.drop() will call dec_request_count
147 bits_service.inc_request_count();
148 let request: RefPtr<BitsRequest> = BitsRequest::allocate(InitBitsRequest {
151 download_pending: Cell::new(true),
152 download_status_nsresult: Cell::new(NS_OK),
153 download_status_error_type: Cell::new(None),
154 monitor_thread: Cell::new(Some(monitor_thread.clone())),
157 started: Cell::new(false),
158 finished: Cell::new(false),
159 cancel_action: Cell::new(CancelAction::NotInProgress),
162 let monitor_runnable =
163 MonitorRunnable::new(request.clone(), id, monitor_timeout_ms, monitor_client);
165 if let Err(rv) = monitor_runnable.dispatch(monitor_thread.clone()) {
166 request.shutdown_monitor_thread();
167 return Err(BitsTaskError::from_nsresult(
168 FailedToDispatchRunnable,
178 pub fn get_monitor_thread(&self) -> Option<RefPtr<nsIThread>> {
179 let monitor_thread = self.monitor_thread.take();
180 self.monitor_thread.set(monitor_thread.clone());
184 fn has_monitor_thread(&self) -> bool {
185 let maybe_monitor_thread = self.monitor_thread.take();
186 let transferred = maybe_monitor_thread.is_some();
187 self.monitor_thread.set(maybe_monitor_thread);
191 /// If this returns an true, it means that:
192 /// - The monitor thread and monitor runnable may have been shut down
193 /// - The BITS job is not in the TRANSFERRING state
194 /// - The download either completed, failed, or was cancelled
195 /// - The BITS job may or may not still need complete() or cancel() to be
197 fn request_has_transferred(&self) -> bool {
198 self.request_has_completed() || !self.has_monitor_thread()
201 /// If this returns an error, it means that:
202 /// - complete() or cancel() has been called on the BITS job.
203 /// - BitsService::dec_request_count has already been called.
204 /// - The BitsClient object that this request was using may have been
206 fn request_has_completed(&self) -> bool {
210 fn shutdown_monitor_thread(&self) {
211 if let Some(monitor_thread) = self.monitor_thread.take() {
212 if let Err(rv) = unsafe { monitor_thread.AsyncShutdown() }.to_result() {
213 warn!("Failed to shut down monitor thread: {:?}", rv);
214 warn!("Releasing reference to thread that failed to shut down!");
220 * To be called when the transfer starts. Fires observer.OnStartRequest exactly once.
222 pub fn on_start(&self) {
223 if self.started.get() {
226 self.started.set(true);
227 if let Err(rv) = unsafe { self.observer.OnStartRequest(self.coerce()) }.to_result() {
228 // This behavior is specified by nsIRequestObserver.
229 // See nsIRequestObserver.idl
231 "Cancelling download because OnStartRequest rejected with: {:?}",
234 if let Err(rv) = self.cancel(NS_ERROR_ABORT, None) {
235 warn!("Failed to cancel download: {:?}", rv);
240 pub fn on_progress(&self, transferred_bytes: i64, total_bytes: i64) {
241 if let Some(progress_event_sink) = self.observer.query_interface::<nsIProgressEventSink>() {
243 progress_event_sink.OnProgress(self.coerce(), transferred_bytes, total_bytes);
248 /// To be called when the transfer stops (fails or completes). Fires
249 /// observer.OnStopRequest exactly once, though the call may be delayed to
250 /// resolve a race condition.
252 /// The status values, if passed, will be stored in download_status_nsresult
253 /// and download_status_error_type, unless they have been overridden by a
256 /// See the documentation for CancelAction for details.
257 pub fn on_stop(&self, maybe_status: Option<(nsresult, Option<ErrorType>)>) {
258 if !self.has_monitor_thread() {
259 // If the request has already stopped, don't stop it again
263 match self.cancel_action.get() {
264 CancelAction::InProgress(saved_status)
265 | CancelAction::RequestEndedWhileInProgress(saved_status) => {
266 if let Some((status, result)) = maybe_status {
267 self.download_status_nsresult.set(status);
268 self.download_status_error_type.set(result);
271 info!("Deferring OnStopRequest until Cancel Task completes");
273 .set(CancelAction::RequestEndedWhileInProgress(saved_status));
276 CancelAction::NotInProgress => {
277 if let Some((status, result)) = maybe_status {
278 self.download_status_nsresult.set(status);
279 self.download_status_error_type.set(result);
282 CancelAction::RequestEndPending => {
283 // Don't set the status variables if the end of this request was
284 // the result of a cancel action. The cancel action already set
285 // those values and they should not be changed.
286 // See the CancelAction documentation for details.
290 self.download_pending.set(false);
291 self.shutdown_monitor_thread();
294 .OnStopRequest(self.coerce(), self.download_status_nsresult.get());
298 /// To be called after a cancel or complete task has run successfully. If
299 /// this is the only BitsRequest running, this will shut down
300 /// BitsService's command thread, destroying the BitsClient.
301 pub fn on_finished(&self) {
302 if self.finished.get() {
305 self.finished.set(true);
306 self.bits_service.dec_request_count();
309 // Return the same thing for GetBitsId() and GetName().
311 maybe_get_bits_id => GetBitsId() -> nsACString
314 maybe_get_bits_id => GetName() -> nsACString
316 fn maybe_get_bits_id(&self) -> Result<nsCString, nsresult> {
317 Ok(self.get_bits_id())
319 pub fn get_bits_id(&self) -> nsCString {
320 nsCString::from(self.bits_id.to_string())
324 get_bits_transfer_error_nsIBitsRequest => GetTransferError() -> i32
326 #[allow(non_snake_case)]
327 fn get_bits_transfer_error_nsIBitsRequest(&self) -> Result<i32, nsresult> {
328 let error_type = match self.download_status_error_type.get() {
329 None => nsIBits::ERROR_TYPE_SUCCESS,
330 Some(error_type) => error_type.bits_code(),
336 is_pending => IsPending() -> bool
338 fn is_pending(&self) -> Result<bool, nsresult> {
339 Ok(self.download_pending.get())
343 get_status_nsIRequest => GetStatus() -> nsresult
345 #[allow(non_snake_case)]
346 fn get_status_nsIRequest(&self) -> Result<nsresult, nsresult> {
347 Ok(self.get_status())
349 pub fn get_status(&self) -> nsresult {
350 self.download_status_nsresult.get()
353 nsIBitsRequest_method!(
354 [Action::SetMonitorInterval]
355 change_monitor_interval => ChangeMonitorInterval(update_interval_ms: u32)
357 fn change_monitor_interval(
359 update_interval_ms: u32,
360 callback: &nsIBitsCallback,
361 ) -> Result<(), BitsTaskError> {
362 if update_interval_ms == 0 || update_interval_ms >= self.monitor_timeout_ms {
363 return Err(BitsTaskError::new(
365 Action::SetMonitorInterval,
369 if self.request_has_transferred() {
370 return Err(BitsTaskError::new(
371 TransferAlreadyComplete,
372 Action::SetMonitorInterval,
377 let task: Box<ChangeMonitorIntervalTask> = Box::new(ChangeMonitorIntervalTask::new(
379 self.bits_id.clone(),
381 RefPtr::new(callback),
384 self.bits_service.dispatch_runnable_to_command_thread(
386 "BitsRequest::change_monitor_interval",
387 Action::SetMonitorInterval,
391 nsIBitsRequest_method!(
393 cancel_nsIBitsRequest => CancelAsync(status: nsresult)
395 #[allow(non_snake_case)]
396 fn cancel_nsIBitsRequest(
399 callback: &nsIBitsCallback,
400 ) -> Result<(), BitsTaskError> {
401 self.cancel(status, Some(RefPtr::new(callback)))
404 cancel_nsIRequest => Cancel(status: nsresult)
406 #[allow(non_snake_case)]
407 fn cancel_nsIRequest(&self, status: nsresult) -> Result<(), BitsTaskError> {
408 self.cancel(status, None)
414 callback: Option<RefPtr<nsIBitsCallback>>,
415 ) -> Result<(), BitsTaskError> {
416 if status.clone().succeeded() {
417 return Err(BitsTaskError::new(InvalidArgument, Action::Cancel, Pretask));
419 if self.request_has_completed() {
420 return Err(BitsTaskError::new(
421 TransferAlreadyComplete,
427 // If the transfer is still in a success state, cancelling it should move it to the failure
428 // state that was passed. But if the transfer already failed, the only reason to call cancel
429 // is to remove the job from BITS. So in that case, we should keep the failure status that
431 let maybe_status: Option<nsresult> = if self.download_status_nsresult.get().failed() {
437 if self.cancel_action.get() != CancelAction::NotInProgress {
438 return Err(BitsTaskError::new(
439 OperationAlreadyInProgress,
445 .set(CancelAction::InProgress(maybe_status));
447 let task: Box<CancelTask> = Box::new(CancelTask::new(
449 self.bits_id.clone(),
453 self.bits_service.dispatch_runnable_to_command_thread(
455 "BitsRequest::cancel",
460 /// This function must be called when a cancel action completes.
462 /// See the documentation for CancelAction for details.
463 pub fn finish_cancel_action(&self, cancelled_successfully: bool) {
464 let (maybe_status, transfer_ended) = match self.cancel_action.get() {
465 CancelAction::InProgress(maybe_status) => (maybe_status, false),
466 CancelAction::RequestEndedWhileInProgress(maybe_status) => (maybe_status, true),
468 error!("End of cancel action, but cancel action is not in progress!");
473 "Finishing cancel action. cancel success = {}",
474 cancelled_successfully
476 if cancelled_successfully {
477 // If no status was provided, it is because this cancel action removed the BITS job
478 // after the job had already failed. Keep the original error codes.
479 if let Some(status) = maybe_status {
480 self.download_status_nsresult.set(status);
481 self.download_status_error_type
482 .set(Some(BitsStateCancelled));
486 let next_stage = if cancelled_successfully && !transfer_ended {
487 // This signals on_stop not to allow the status codes set above to
488 // be overridden by the ones passed to it.
489 CancelAction::RequestEndPending
491 CancelAction::NotInProgress
493 self.cancel_action.set(next_stage);
495 if cancelled_successfully {
500 info!("Running deferred OnStopRequest");
505 nsIBitsRequest_method!(
506 [Action::SetPriority]
507 set_priority_high => SetPriorityHigh()
509 fn set_priority_high(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
510 self.set_priority(Priority::High, callback)
513 nsIBitsRequest_method!(
514 [Action::SetPriority]
515 set_priority_low => SetPriorityLow()
517 fn set_priority_low(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
518 self.set_priority(Priority::Low, callback)
524 callback: &nsIBitsCallback,
525 ) -> Result<(), BitsTaskError> {
526 if self.request_has_transferred() {
527 return Err(BitsTaskError::new(
528 TransferAlreadyComplete,
534 let task: Box<SetPriorityTask> = Box::new(SetPriorityTask::new(
536 self.bits_id.clone(),
538 RefPtr::new(callback),
541 self.bits_service.dispatch_runnable_to_command_thread(
543 "BitsRequest::set_priority",
548 nsIBitsRequest_method!(
549 [Action::SetNoProgressTimeout]
550 set_no_progress_timeout => SetNoProgressTimeout(timeout_secs: u32)
552 fn set_no_progress_timeout(
555 callback: &nsIBitsCallback,
556 ) -> Result<(), BitsTaskError> {
557 if self.request_has_transferred() {
558 return Err(BitsTaskError::new(
559 TransferAlreadyComplete,
560 Action::SetNoProgressTimeout,
565 let task: Box<SetNoProgressTimeoutTask> = Box::new(SetNoProgressTimeoutTask::new(
567 self.bits_id.clone(),
569 RefPtr::new(callback),
572 self.bits_service.dispatch_runnable_to_command_thread(
574 "BitsRequest::set_no_progress_timeout",
575 Action::SetNoProgressTimeout,
579 nsIBitsRequest_method!(
581 complete => Complete()
583 fn complete(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
584 if self.request_has_completed() {
585 return Err(BitsTaskError::new(
586 TransferAlreadyComplete,
592 let task: Box<CompleteTask> = Box::new(CompleteTask::new(
594 self.bits_id.clone(),
595 RefPtr::new(callback),
598 self.bits_service.dispatch_runnable_to_command_thread(
600 "BitsRequest::complete",
605 nsIBitsRequest_method!(
607 suspend_nsIBitsRequest => SuspendAsync()
609 #[allow(non_snake_case)]
610 fn suspend_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
611 self.suspend(Some(RefPtr::new(callback)))
614 suspend_nsIRequest => Suspend()
616 #[allow(non_snake_case)]
617 fn suspend_nsIRequest(&self) -> Result<(), BitsTaskError> {
621 fn suspend(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
622 if self.request_has_transferred() {
623 return Err(BitsTaskError::new(
624 TransferAlreadyComplete,
630 let task: Box<SuspendTask> = Box::new(SuspendTask::new(
632 self.bits_id.clone(),
636 self.bits_service.dispatch_runnable_to_command_thread(
638 "BitsRequest::suspend",
643 nsIBitsRequest_method!(
645 resume_nsIBitsRequest => ResumeAsync()
647 #[allow(non_snake_case)]
648 fn resume_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
649 self.resume(Some(RefPtr::new(callback)))
652 resume_nsIRequest => Resume()
654 #[allow(non_snake_case)]
655 fn resume_nsIRequest(&self) -> Result<(), BitsTaskError> {
659 fn resume(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
660 if self.request_has_transferred() {
661 return Err(BitsTaskError::new(
662 TransferAlreadyComplete,
668 let task: Box<ResumeTask> = Box::new(ResumeTask::new(
670 self.bits_id.clone(),
674 self.bits_service.dispatch_runnable_to_command_thread(
676 "BitsRequest::resume",
682 get_load_group => GetLoadGroup() -> *const nsILoadGroup
686 * As stated in nsIBits.idl, nsIBits interfaces are not expected to
687 * implement the loadGroup or loadFlags attributes. This implementation
688 * provides only null implementations only for these methods.
690 fn get_load_group(&self) -> Result<RefPtr<nsILoadGroup>, nsresult> {
691 Err(NS_ERROR_NOT_IMPLEMENTED)
695 set_load_group => SetLoadGroup(_load_group: *const nsILoadGroup)
697 fn set_load_group(&self, _load_group: &nsILoadGroup) -> Result<(), nsresult> {
698 Err(NS_ERROR_NOT_IMPLEMENTED)
702 get_load_flags => GetLoadFlags() -> nsLoadFlags
704 fn get_load_flags(&self) -> Result<nsLoadFlags, nsresult> {
705 Err(NS_ERROR_NOT_IMPLEMENTED)
709 set_load_flags => SetLoadFlags(_load_flags: nsLoadFlags)
711 fn set_load_flags(&self, _load_flags: nsLoadFlags) -> Result<(), nsresult> {
712 Err(NS_ERROR_NOT_IMPLEMENTED)
716 get_trr_mode => GetTRRMode() -> u32
718 fn get_trr_mode(&self) -> Result<u32, nsresult> {
719 Err(NS_ERROR_NOT_IMPLEMENTED)
723 set_trr_mode => SetTRRMode(_trr_mode: u32)
725 fn set_trr_mode(&self, _trr_mode: u32) -> Result<(), nsresult> {
726 Err(NS_ERROR_NOT_IMPLEMENTED)
730 get_canceled_reason => GetCanceledReason() -> nsACString
732 fn get_canceled_reason(&self) -> Result<nsCString, nsresult> {
733 Err(NS_ERROR_NOT_IMPLEMENTED)
737 set_canceled_reason => SetCanceledReason(_reason: *const nsACString)
739 fn set_canceled_reason(&self, _reason: *const nsACString) -> Result<(), nsresult> {
740 Err(NS_ERROR_NOT_IMPLEMENTED)
744 cancel_with_reason_nsIRequest => CancelWithReason(status: nsresult, _reason: *const nsACString)
746 #[allow(non_snake_case)]
747 fn cancel_with_reason_nsIRequest(
750 _reason: *const nsACString,
751 ) -> Result<(), BitsTaskError> {
752 self.cancel(status, None)
756 impl Drop for BitsRequest {
758 // Make sure that the monitor thread gets cleaned up.
759 self.shutdown_monitor_thread();
760 // Make sure we tell BitsService that we are done with the command thread.