Skip to main content

glean_core/upload/
mod.rs

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 https://mozilla.org/MPL/2.0/.
4
5//! Manages the pending pings queue and directory.
6//!
7//! * Keeps track of pending pings, loading any unsent ping from disk on startup;
8//! * Exposes [`get_upload_task`](PingUploadManager::get_upload_task) API for
9//!   the platform layer to request next upload task;
10//! * Exposes
11//!   [`process_ping_upload_response`](PingUploadManager::process_ping_upload_response)
12//!   API to check the HTTP response from the ping upload and either delete the
13//!   corresponding ping from disk or re-enqueue it for sending.
14
15use std::collections::HashMap;
16use std::collections::VecDeque;
17use std::mem;
18use std::path::PathBuf;
19use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
20use std::sync::{Arc, RwLock, RwLockWriteGuard};
21use std::time::{Duration, Instant};
22
23use chrono::Utc;
24use malloc_size_of::MallocSizeOf;
25use malloc_size_of_derive::MallocSizeOf;
26
27use crate::error::ErrorKind;
28use crate::TimerId;
29use crate::{internal_metrics::UploadMetrics, Glean};
30pub use directory::process_metadata;
31use directory::{PingDirectoryManager, PingPayloadsByDirectory};
32use policy::Policy;
33use request::create_date_header_value;
34
35use crate::database::StoredSubmittedPingHandler;
36pub use directory::{PingMetadata, PingPayload};
37pub use request::{HeaderMap, PingRequest};
38pub use result::{UploadResult, UploadTaskAction};
39
40mod directory;
41mod policy;
42mod request;
43mod result;
44
45const WAIT_TIME_FOR_PING_PROCESSING: u64 = 1000; // in milliseconds
46
47#[derive(Debug, MallocSizeOf)]
48struct RateLimiter {
49    /// The instant the current interval has started.
50    started: Option<Instant>,
51    /// The count for the current interval.
52    count: u32,
53    /// The duration of each interval.
54    interval: Duration,
55    /// The maximum count per interval.
56    max_count: u32,
57}
58
59/// An enum to represent the current state of the RateLimiter.
60#[derive(PartialEq)]
61enum RateLimiterState {
62    /// The RateLimiter has not reached the maximum count and is still incrementing.
63    Incrementing,
64    /// The RateLimiter has reached the maximum count for the  current interval.
65    ///
66    /// This variant contains the remaining time (in milliseconds)
67    /// until the rate limiter is not throttled anymore.
68    Throttled(u64),
69}
70
71impl RateLimiter {
72    pub fn new(interval: Duration, max_count: u32) -> Self {
73        Self {
74            started: None,
75            count: 0,
76            interval,
77            max_count,
78        }
79    }
80
81    fn reset(&mut self) {
82        self.started = Some(Instant::now());
83        self.count = 0;
84    }
85
86    fn elapsed(&self) -> Duration {
87        self.started.unwrap().elapsed()
88    }
89
90    // The counter should reset if
91    //
92    // 1. It has never started;
93    // 2. It has been started more than the interval time ago;
94    // 3. Something goes wrong while trying to calculate the elapsed time since the last reset.
95    fn should_reset(&self) -> bool {
96        if self.started.is_none() {
97            return true;
98        }
99
100        // Safe unwrap, we already stated that `self.started` is not `None` above.
101        if self.elapsed() > self.interval {
102            return true;
103        }
104
105        false
106    }
107
108    /// Tries to increment the internal counter.
109    ///
110    /// # Returns
111    ///
112    /// The current state of the RateLimiter.
113    pub fn get_state(&mut self) -> RateLimiterState {
114        if self.should_reset() {
115            self.reset();
116        }
117
118        if self.count == self.max_count {
119            // Note that `remining` can't be a negative number because we just called `reset`,
120            // which will check if it is and reset if so.
121            let remaining = self.interval.as_millis() - self.elapsed().as_millis();
122            return RateLimiterState::Throttled(
123                remaining
124                    .try_into()
125                    .unwrap_or(self.interval.as_secs() * 1000),
126            );
127        }
128
129        self.count += 1;
130        RateLimiterState::Incrementing
131    }
132}
133
134/// An enum representing the possible upload tasks to be performed by an uploader.
135///
136/// When asking for the next ping request to upload,
137/// the requester may receive one out of three possible tasks.
138#[derive(PartialEq, Eq, Debug)]
139pub enum PingUploadTask {
140    /// An upload task
141    Upload {
142        /// The ping request for upload
143        /// See [`PingRequest`](struct.PingRequest.html) for more information.
144        request: PingRequest,
145    },
146
147    /// A flag signaling that the pending pings directories are not done being processed,
148    /// thus the requester should wait and come back later.
149    Wait {
150        /// The time in milliseconds
151        /// the requester should wait before requesting a new task.
152        time: u64,
153    },
154
155    /// A flag signaling that requester doesn't need to request any more upload tasks at this moment.
156    ///
157    /// There are three possibilities for this scenario:
158    /// * Pending pings queue is empty, no more pings to request;
159    /// * Requester has gotten more than MAX_WAIT_ATTEMPTS (3, by default) `PingUploadTask::Wait` responses in a row;
160    /// * Requester has reported more than MAX_RECOVERABLE_FAILURES_PER_UPLOADING_WINDOW
161    ///   recoverable upload failures on the same uploading window (see below)
162    ///   and should stop requesting at this moment.
163    ///
164    /// An "uploading window" starts when a requester gets a new
165    /// `PingUploadTask::Upload(PingRequest)` response and finishes when they
166    /// finally get a `PingUploadTask::Done` or `PingUploadTask::Wait` response.
167    Done {
168        #[doc(hidden)]
169        /// Unused field. Required because UniFFI can't handle variants without fields.
170        unused: i8,
171    },
172}
173
174impl PingUploadTask {
175    /// Whether the current task is an upload task.
176    pub fn is_upload(&self) -> bool {
177        matches!(self, PingUploadTask::Upload { .. })
178    }
179
180    /// Whether the current task is wait task.
181    pub fn is_wait(&self) -> bool {
182        matches!(self, PingUploadTask::Wait { .. })
183    }
184
185    pub(crate) fn done() -> Self {
186        PingUploadTask::Done { unused: 0 }
187    }
188}
189
190/// Manages the pending pings queue and directory.
191#[derive(Debug)]
192pub struct PingUploadManager {
193    /// A FIFO queue storing a `PingRequest` for each pending ping.
194    queue: RwLock<VecDeque<PingRequest>>,
195    /// A manager for the pending pings directories.
196    directory_manager: PingDirectoryManager,
197    /// A flag signaling if we are done processing the pending pings directories.
198    processed_pending_pings: Arc<AtomicBool>,
199    /// A vector to store the pending pings processed off-thread.
200    cached_pings: Arc<RwLock<PingPayloadsByDirectory>>,
201    /// The number of upload failures for the current uploading window.
202    recoverable_failure_count: AtomicU32,
203    /// The number or times in a row a user has received a `PingUploadTask::Wait` response.
204    wait_attempt_count: AtomicU32,
205    /// A ping counter to help rate limit the ping uploads.
206    ///
207    /// To keep resource usage in check,
208    /// we may want to limit the amount of pings sent in a given interval.
209    rate_limiter: Option<RwLock<RateLimiter>>,
210    /// The name of the programming language used by the binding creating this instance of PingUploadManager.
211    ///
212    /// This will be used to build the value User-Agent header for each ping request.
213    language_binding_name: String,
214    /// Metrics related to ping uploading.
215    upload_metrics: UploadMetrics,
216    /// Policies for ping storage, uploading and requests.
217    policy: Policy,
218
219    in_flight: RwLock<HashMap<String, (TimerId, TimerId)>>,
220}
221
222impl MallocSizeOf for PingUploadManager {
223    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
224        let shallow_size = {
225            let queue = self.queue.read().unwrap();
226            if ops.has_malloc_enclosing_size_of() {
227                if let Some(front) = queue.front() {
228                    // SAFETY: The front element is a valid interior pointer and thus valid to pass
229                    // to an external function.
230                    unsafe { ops.malloc_enclosing_size_of(front) }
231                } else {
232                    // This assumes that no memory is allocated when the VecDeque is empty.
233                    0
234                }
235            } else {
236                // If `ops` can't estimate the size of a pointer,
237                // we can estimate the allocation size by the size of each element and the
238                // allocated capacity.
239                queue.capacity() * mem::size_of::<PingRequest>()
240            }
241        };
242
243        let mut n = shallow_size
244            + self.directory_manager.size_of(ops)
245            + mem::size_of::<AtomicBool>() // Allocated inside the `self.processed_pending_pings` `Arc`.
246            + self.cached_pings.read().unwrap().size_of(ops)
247            + self.rate_limiter.as_ref().map(|rl| {
248                let lock = rl.read().unwrap();
249                (*lock).size_of(ops)
250            }).unwrap_or(0)
251            + self.language_binding_name.size_of(ops)
252            + self.upload_metrics.size_of(ops)
253            + self.policy.size_of(ops);
254
255        let in_flight = self.in_flight.read().unwrap();
256        n += in_flight.size_of(ops);
257
258        n
259    }
260}
261
262impl PingUploadManager {
263    /// Creates a new PingUploadManager.
264    ///
265    /// # Arguments
266    ///
267    /// * `data_path` - Path to the pending pings directory.
268    /// * `language_binding_name` - The name of the language binding calling this managers instance.
269    ///
270    /// # Panics
271    ///
272    /// Will panic if unable to spawn a new thread.
273    pub fn new<P: Into<PathBuf>>(data_path: P, language_binding_name: &str) -> Self {
274        Self {
275            queue: RwLock::new(VecDeque::new()),
276            directory_manager: PingDirectoryManager::new(data_path),
277            processed_pending_pings: Arc::new(AtomicBool::new(false)),
278            cached_pings: Arc::new(RwLock::new(PingPayloadsByDirectory::default())),
279            recoverable_failure_count: AtomicU32::new(0),
280            wait_attempt_count: AtomicU32::new(0),
281            rate_limiter: None,
282            language_binding_name: language_binding_name.into(),
283            upload_metrics: UploadMetrics::new(),
284            policy: Policy::default(),
285            in_flight: RwLock::new(HashMap::default()),
286        }
287    }
288
289    /// Spawns a new thread and processes the pending pings directories,
290    /// filling up the queue with whatever pings are in there.
291    ///
292    /// # Returns
293    ///
294    /// The `JoinHandle` to the spawned thread
295    pub fn scan_pending_pings_directories(
296        &self,
297        trigger_upload: bool,
298    ) -> std::thread::JoinHandle<()> {
299        let local_manager = self.directory_manager.clone();
300        let local_cached_pings = self.cached_pings.clone();
301        let local_flag = self.processed_pending_pings.clone();
302        crate::thread::spawn("glean.ping_directory_manager.process_dir", move || {
303            {
304                // Be sure to drop local_cached_pings lock before triggering upload.
305                let mut local_cached_pings = local_cached_pings
306                    .write()
307                    .expect("Can't write to pending pings cache.");
308                local_cached_pings.extend(local_manager.process_dirs());
309                local_flag.store(true, Ordering::SeqCst);
310            }
311            if trigger_upload {
312                crate::dispatcher::launch(|| {
313                    if let Some(state) = crate::maybe_global_state().and_then(|s| s.lock().ok()) {
314                        if let Err(e) = state.callbacks.trigger_upload() {
315                            log::error!(
316                                "Triggering upload after pending ping scan failed. Error: {}",
317                                e
318                            );
319                        }
320                    }
321                });
322            }
323        })
324        .expect("Unable to spawn thread to process pings directories.")
325    }
326
327    /// Creates a new upload manager with no limitations, for tests.
328    #[cfg(test)]
329    pub fn no_policy<P: Into<PathBuf>>(data_path: P) -> Self {
330        let mut upload_manager = Self::new(data_path, "Test");
331
332        // Disable all policies for tests, if necessary individuals tests can re-enable them.
333        upload_manager.policy.set_max_recoverable_failures(None);
334        upload_manager.policy.set_max_wait_attempts(None);
335        upload_manager.policy.set_max_ping_body_size(None);
336        upload_manager
337            .policy
338            .set_max_pending_pings_directory_size(None);
339        upload_manager.policy.set_max_pending_pings_count(None);
340
341        // When building for tests, always scan the pending pings directories and do it sync.
342        upload_manager
343            .scan_pending_pings_directories(false)
344            .join()
345            .unwrap();
346
347        upload_manager
348    }
349
350    fn processed_pending_pings(&self) -> bool {
351        self.processed_pending_pings.load(Ordering::SeqCst)
352    }
353
354    fn recoverable_failure_count(&self) -> u32 {
355        self.recoverable_failure_count.load(Ordering::SeqCst)
356    }
357
358    fn wait_attempt_count(&self) -> u32 {
359        self.wait_attempt_count.load(Ordering::SeqCst)
360    }
361
362    /// Attempts to build a ping request from a ping file payload.
363    ///
364    /// Returns the `PingRequest` or `None` if unable to build,
365    /// in which case it will delete the ping file and record an error.
366    fn build_ping_request(&self, glean: &Glean, ping: PingPayload) -> Option<PingRequest> {
367        let PingPayload {
368            document_id,
369            upload_path: path,
370            json_body: body,
371            headers,
372            body_has_info_sections,
373            ping_name,
374            uploader_capabilities,
375        } = ping;
376        let mut request = PingRequest::builder(
377            &self.language_binding_name,
378            self.policy.max_ping_body_size(),
379        )
380        .document_id(&document_id)
381        .path(path)
382        .body(body)
383        .body_has_info_sections(body_has_info_sections)
384        .ping_name(ping_name)
385        .uploader_capabilities(uploader_capabilities);
386
387        if let Some(headers) = headers {
388            request = request.headers(headers);
389        }
390
391        match request.build() {
392            Ok(request) => Some(request),
393            Err(e) => {
394                log::warn!("Error trying to build ping request: {}", e);
395                self.directory_manager.delete_file(&document_id);
396
397                // Record the error.
398                // Currently the only possible error is PingBodyOverflow.
399                if let ErrorKind::PingBodyOverflow(s) = e.kind() {
400                    self.upload_metrics
401                        .discarded_exceeding_pings_size
402                        .accumulate_sync(glean, *s as i64 / 1024);
403                }
404
405                None
406            }
407        }
408    }
409
410    /// Enqueue a ping for upload.
411    pub fn enqueue_ping(&self, glean: &Glean, ping: PingPayload) {
412        let mut queue = self
413            .queue
414            .write()
415            .expect("Can't write to pending pings queue.");
416
417        let PingPayload {
418            ref document_id,
419            upload_path: ref path,
420            ..
421        } = ping;
422        // Checks if a ping with this `document_id` is already enqueued.
423        if queue
424            .iter()
425            .any(|request| request.document_id.as_str() == document_id)
426        {
427            log::warn!(
428                "Attempted to enqueue a duplicate ping {} at {}.",
429                document_id,
430                path
431            );
432            return;
433        }
434
435        {
436            let in_flight = self.in_flight.read().unwrap();
437            if in_flight.contains_key(document_id) {
438                log::warn!(
439                    "Attempted to enqueue an in-flight ping {} at {}.",
440                    document_id,
441                    path
442                );
443                self.upload_metrics
444                    .in_flight_pings_dropped
445                    .add_sync(glean, 0);
446                return;
447            }
448        }
449
450        log::trace!("Enqueuing ping {} at {}", document_id, path);
451        if let Some(request) = self.build_ping_request(glean, ping) {
452            queue.push_back(request)
453        }
454    }
455
456    /// Enqueues pings that might have been cached.
457    ///
458    /// The size of the PENDING_PINGS_DIRECTORY directory will be calculated
459    /// (by accumulating each ping's size in that directory)
460    /// and in case we exceed the quota, defined by the `quota` arg,
461    /// outstanding pings get deleted and are not enqueued.
462    ///
463    /// The size of the DELETION_REQUEST_PINGS_DIRECTORY will not be calculated
464    /// and no deletion-request pings will be deleted. Deletion request pings
465    /// are not very common and usually don't contain any data,
466    /// we don't expect that directory to ever reach quota.
467    /// Most importantly, we don't want to ever delete deletion-request pings.
468    ///
469    /// # Arguments
470    ///
471    /// * `glean` - The Glean object holding the database.
472    fn enqueue_cached_pings(&self, glean: &Glean) {
473        let mut cached_pings = self
474            .cached_pings
475            .write()
476            .expect("Can't write to pending pings cache.");
477
478        if cached_pings.len() > 0 {
479            let mut pending_pings_directory_size: u64 = 0;
480            let mut pending_pings_count = 0;
481            let mut deleting = false;
482            let mut delete_reason: Option<&'static str> = None;
483
484            let total = cached_pings.pending_pings.len() as u64;
485            self.upload_metrics
486                .pending_pings
487                .add_sync(glean, total.try_into().unwrap_or(0));
488
489            if total > self.policy.max_pending_pings_count() {
490                log::warn!(
491                    "More than {} pending pings in the directory, will delete {} old pings.",
492                    self.policy.max_pending_pings_count(),
493                    total - self.policy.max_pending_pings_count()
494                );
495            }
496
497            // The pending pings vector is sorted by date in ascending order (oldest -> newest).
498            // We need to calculate the size of the pending pings directory
499            // and delete the **oldest** pings in case quota is reached.
500            // Thus, we reverse the order of the pending pings vector,
501            // so that we iterate in descending order (newest -> oldest).
502            cached_pings.pending_pings.reverse();
503            cached_pings.pending_pings.retain(|(file_size, PingPayload {document_id, ..})| {
504                pending_pings_count += 1;
505                pending_pings_directory_size += file_size;
506
507                // We don't want to spam the log for every ping over the quota.
508                // Size is checked first; if both limits are exceeded simultaneously,
509                // size_quota takes precedence as the recorded reason.
510                if !deleting && pending_pings_directory_size > self.policy.max_pending_pings_directory_size() {
511                    log::warn!(
512                        "Pending pings directory has reached the size quota of {} bytes, outstanding pings will be deleted.",
513                        self.policy.max_pending_pings_directory_size()
514                    );
515                    deleting = true;
516                    delete_reason = Some("size_quota");
517                }
518
519                // Once we reach the number of allowed pings we start deleting,
520                // no matter what size.
521                // We already log this before the loop.
522                if !deleting && pending_pings_count > self.policy.max_pending_pings_count() {
523                    deleting = true;
524                    delete_reason = Some("count_quota");
525                }
526
527                if deleting && self.directory_manager.delete_file(document_id) {
528                    self.upload_metrics
529                        .deleted_pings_after_quota_hit
530                        .add_sync(glean, 1);
531                    if let Some(reason) = delete_reason {
532                        self.upload_metrics
533                            .pending_pings_deleted
534                            .get(reason)
535                            .add_sync(glean, 1);
536                    }
537                    return false;
538                }
539
540                true
541            });
542            // After calculating the size of the pending pings directory,
543            // we record the calculated number and reverse the pings array back for enqueueing.
544            cached_pings.pending_pings.reverse();
545            self.upload_metrics
546                .pending_pings_directory_size
547                .accumulate_sync(glean, pending_pings_directory_size as i64 / 1024);
548
549            // Enqueue the remaining pending pings and
550            // enqueue all deletion-request pings.
551            cached_pings
552                .deletion_request_pings
553                .drain(..)
554                .for_each(|(_, ping)| self.enqueue_ping(glean, ping));
555            cached_pings
556                .pending_pings
557                .drain(..)
558                .for_each(|(_, ping)| self.enqueue_ping(glean, ping));
559        }
560    }
561
562    /// Adds rate limiting capability to this upload manager.
563    ///
564    /// The rate limiter will limit the amount of calls to `get_upload_task` per interval.
565    ///
566    /// Setting this will restart count and timer in case there was a previous rate limiter set
567    /// (e.g. if we have reached the current limit and call this function, we start counting again
568    /// and the caller is allowed to asks for tasks).
569    ///
570    /// # Arguments
571    ///
572    /// * `interval` - the amount of seconds in each rate limiting window.
573    /// * `max_tasks` - the maximum amount of task requests allowed per interval.
574    pub fn set_rate_limiter(&mut self, interval: u64, max_tasks: u32) {
575        self.rate_limiter = Some(RwLock::new(RateLimiter::new(
576            Duration::from_secs(interval),
577            max_tasks,
578        )));
579    }
580
581    pub(crate) fn set_max_pending_pings_count(&mut self, n: u64) {
582        self.policy.set_max_pending_pings_count(Some(n));
583    }
584
585    pub(crate) fn set_max_pending_pings_directory_size(&mut self, n: u64) {
586        self.policy.set_max_pending_pings_directory_size(Some(n));
587    }
588
589    /// Reads a ping file, creates a `PingRequest` and adds it to the queue.
590    ///
591    /// Duplicate requests won't be added.
592    ///
593    /// # Arguments
594    ///
595    /// * `glean` - The Glean object holding the database.
596    /// * `document_id` - The UUID of the ping in question.
597    pub fn enqueue_ping_from_file(&self, glean: &Glean, document_id: &str) {
598        if let Some(ping) = self.directory_manager.process_file(document_id) {
599            self.enqueue_ping(glean, ping);
600        }
601    }
602
603    /// Clears the pending pings queue, leaves the deletion-request pings.
604    pub fn clear_ping_queue(&self) -> RwLockWriteGuard<'_, VecDeque<PingRequest>> {
605        log::trace!("Clearing ping queue");
606        let mut queue = self
607            .queue
608            .write()
609            .expect("Can't write to pending pings queue.");
610
611        queue.retain(|ping| ping.is_deletion_request());
612        log::trace!(
613            "{} pings left in the queue (only deletion-request expected)",
614            queue.len()
615        );
616        queue
617    }
618
619    fn get_upload_task_internal(&self, glean: &Glean, log_ping: bool) -> PingUploadTask {
620        // Helper to decide whether to return PingUploadTask::Wait or PingUploadTask::Done.
621        //
622        // We want to limit the amount of PingUploadTask::Wait returned in a row,
623        // in case we reach MAX_WAIT_ATTEMPTS we want to actually return PingUploadTask::Done.
624        let wait_or_done = |time: u64| {
625            self.wait_attempt_count.fetch_add(1, Ordering::SeqCst);
626            if self.wait_attempt_count() > self.policy.max_wait_attempts() {
627                PingUploadTask::done()
628            } else {
629                PingUploadTask::Wait { time }
630            }
631        };
632
633        if !self.processed_pending_pings() {
634            log::info!(
635                "Tried getting an upload task, but processing is ongoing. Will come back later."
636            );
637            return wait_or_done(WAIT_TIME_FOR_PING_PROCESSING);
638        }
639
640        // This is a no-op in case there are no cached pings.
641        self.enqueue_cached_pings(glean);
642
643        if self.recoverable_failure_count() >= self.policy.max_recoverable_failures() {
644            log::warn!(
645                "Reached maximum recoverable failures for the current uploading window. You are done."
646            );
647            return PingUploadTask::done();
648        }
649
650        let mut queue = self
651            .queue
652            .write()
653            .expect("Can't write to pending pings queue.");
654        match queue.front() {
655            Some(request) => {
656                if let Some(rate_limiter) = &self.rate_limiter {
657                    let mut rate_limiter = rate_limiter
658                        .write()
659                        .expect("Can't write to the rate limiter.");
660                    if let RateLimiterState::Throttled(remaining) = rate_limiter.get_state() {
661                        log::info!(
662                            "Tried getting an upload task, but we are throttled at the moment."
663                        );
664                        return wait_or_done(remaining);
665                    }
666                }
667
668                log::info!(
669                    "New upload task with id {} (path: {})",
670                    request.document_id,
671                    request.path
672                );
673
674                if log_ping {
675                    if let Some(body) = request.pretty_body() {
676                        chunked_log_info(&request.path, &body);
677                    } else {
678                        chunked_log_info(&request.path, "<invalid ping payload>");
679                    }
680                }
681
682                {
683                    // Synchronous timer starts.
684                    // We're in the uploader thread anyway.
685                    // But also: No data is stored on disk.
686                    let mut in_flight = self.in_flight.write().unwrap();
687                    let success_id = self.upload_metrics.send_success.start_sync();
688                    let failure_id = self.upload_metrics.send_failure.start_sync();
689                    in_flight.insert(request.document_id.clone(), (success_id, failure_id));
690                }
691
692                let mut request = queue.pop_front().unwrap();
693
694                // Adding the `Date` header just before actual upload happens.
695                request
696                    .headers
697                    .insert("Date".to_string(), create_date_header_value(Utc::now()));
698
699                PingUploadTask::Upload { request }
700            }
701            None => {
702                log::info!("No more pings to upload! You are done.");
703                PingUploadTask::done()
704            }
705        }
706    }
707
708    /// Gets the next `PingUploadTask`.
709    ///
710    /// # Arguments
711    ///
712    /// * `glean` - The Glean object holding the database.
713    /// * `log_ping` - Whether to log the ping before returning.
714    ///
715    /// # Returns
716    ///
717    /// The next [`PingUploadTask`](enum.PingUploadTask.html).
718    pub fn get_upload_task(&self, glean: &Glean, log_ping: bool) -> PingUploadTask {
719        let task = self.get_upload_task_internal(glean, log_ping);
720
721        if !task.is_wait() && self.wait_attempt_count() > 0 {
722            self.wait_attempt_count.store(0, Ordering::SeqCst);
723        }
724
725        if !task.is_upload() && self.recoverable_failure_count() > 0 {
726            self.recoverable_failure_count.store(0, Ordering::SeqCst);
727        }
728
729        task
730    }
731
732    /// Processes the response from an attempt to upload a ping.
733    ///
734    /// Based on the HTTP status of said response,
735    /// the possible outcomes are:
736    ///
737    /// * **200 - 299 Success**
738    ///   Any status on the 2XX range is considered a succesful upload,
739    ///   which means the corresponding ping file can be deleted.
740    ///   _Known 2XX status:_
741    ///   * 200 - OK. Request accepted into the pipeline.
742    ///
743    /// * **408 Request timeout - Recoverable error**
744    ///   The endpoint might be currently timing out. A later request might succeed again.
745    /// * **429 Too Many Requests - Recoverable error**
746    ///   A client might have sent too many requests and the endpoint is rejecting requests
747    ///   for the moment.
748    /// * **400 - 499 Unrecoverable error**
749    ///   Any other status on the 4XX range means something our client did is not correct.
750    ///   It is unlikely that the client is going to recover from this by retrying,
751    ///   so in this case the corresponding ping file can also be deleted.
752    ///   _Known 4XX status:_
753    ///   * 404 - not found - POST/PUT to an unknown namespace
754    ///   * 405 - wrong request type (anything other than POST/PUT)
755    ///   * 411 - missing content-length header
756    ///   * 413 - request body too large Note that if we have badly-behaved clients that
757    ///           retry on 4XX, we should send back 202 on body/path too long).
758    ///   * 414 - request path too long (See above)
759    ///
760    /// * **Any other error**
761    ///   For any other error, a warning is logged and the ping is re-enqueued.
762    ///   _Known other errors:_
763    ///   * 500 - internal error
764    ///
765    /// # Note
766    ///
767    /// The disk I/O performed by this function is not done off-thread,
768    /// as it is expected to be called off-thread by the platform.
769    ///
770    /// # Arguments
771    ///
772    /// * `glean` - The Glean object holding the database.
773    /// * `document_id` - The UUID of the ping in question.
774    /// * `status` - The HTTP status of the response.
775    pub fn process_ping_upload_response(
776        &self,
777        glean: &Glean,
778        document_id: &str,
779        status: UploadResult,
780    ) -> UploadTaskAction {
781        use UploadResult::*;
782
783        let stop_time = zeitstempel::now_awake();
784
785        if let Some(label) = status.get_label() {
786            let metric = self.upload_metrics.ping_upload_failure.get(label);
787            metric.add_sync(glean, 1);
788        }
789
790        let send_ids = {
791            let mut lock = self.in_flight.write().unwrap();
792            lock.remove(document_id)
793        };
794
795        if send_ids.is_none() {
796            self.upload_metrics.missing_send_ids.add_sync(glean, 1);
797        }
798
799        let recoverable = || {
800            log::warn!(
801                    "Recoverable upload failure while attempting to send ping {}, will retry. Error was {:?}",
802                    document_id,
803                    status
804                );
805            if let Some((success_id, failure_id)) = send_ids {
806                self.upload_metrics.send_success.cancel_sync(success_id);
807                self.upload_metrics
808                    .send_failure
809                    .set_stop_and_accumulate(glean, failure_id, stop_time);
810            }
811            self.enqueue_ping_from_file(glean, document_id);
812            self.recoverable_failure_count
813                .fetch_add(1, Ordering::SeqCst);
814        };
815
816        match status {
817            HttpStatus { code } if (200..=299).contains(&code) => {
818                log::info!("Ping {} successfully sent {}.", document_id, code);
819                if let Some((success_id, failure_id)) = send_ids {
820                    self.upload_metrics
821                        .send_success
822                        .set_stop_and_accumulate(glean, success_id, stop_time);
823                    self.upload_metrics.send_failure.cancel_sync(failure_id);
824                }
825                if glean.store_submitted_pings_enabled {
826                    glean
827                        .storage()
828                        .mark_ping_as_uploaded(document_id, Utc::now());
829                }
830                self.directory_manager.delete_file(document_id);
831            }
832
833            RecoverableFailure { .. } => {
834                recoverable();
835            }
836
837            // Special-casing 408 - Request timeout, and 429 - Too Many Requests
838            HttpStatus { code: 408 } | HttpStatus { code: 429 } => {
839                recoverable();
840            }
841
842            UnrecoverableFailure { .. } | HttpStatus { code: 400..=499 } | Incapable { .. } => {
843                log::warn!(
844                    "Unrecoverable upload failure while attempting to send ping {}. Error was {:?}",
845                    document_id,
846                    status
847                );
848                if let Some((success_id, failure_id)) = send_ids {
849                    self.upload_metrics.send_success.cancel_sync(success_id);
850                    self.upload_metrics
851                        .send_failure
852                        .set_stop_and_accumulate(glean, failure_id, stop_time);
853                }
854                if glean.store_submitted_pings_enabled {
855                    glean.storage().mark_ping_as_upload_failed(document_id);
856                }
857                self.directory_manager.delete_file(document_id);
858            }
859
860            // All other HTTP status codes are treated recoverable.
861            HttpStatus { .. } => {
862                recoverable();
863            }
864
865            Done { .. } => {
866                log::debug!("Uploader signaled Done. Exiting.");
867                if let Some((success_id, failure_id)) = send_ids {
868                    self.upload_metrics.send_success.cancel_sync(success_id);
869                    self.upload_metrics.send_failure.cancel_sync(failure_id);
870                }
871                return UploadTaskAction::End;
872            }
873        };
874
875        UploadTaskAction::Next
876    }
877}
878
879/// Splits log message into chunks on Android.
880#[cfg(target_os = "android")]
881pub fn chunked_log_info(path: &str, payload: &str) {
882    // Since the logcat ring buffer size is configurable, but it's 'max payload' size is not,
883    // we must break apart long pings into chunks no larger than the max payload size of 4076b.
884    // We leave some head space for our prefix.
885    const MAX_LOG_PAYLOAD_SIZE_BYTES: usize = 4000;
886
887    // If the length of the ping will fit within one logcat payload, then we can
888    // short-circuit here and avoid some overhead, otherwise we must split up the
889    // message so that we don't truncate it.
890    if path.len() + payload.len() <= MAX_LOG_PAYLOAD_SIZE_BYTES {
891        log::info!("Glean ping to URL: {}\n{}", path, payload);
892        return;
893    }
894
895    // Otherwise we break it apart into chunks of smaller size,
896    // prefixing it with the path and a counter.
897    let mut start = 0;
898    let mut end = MAX_LOG_PAYLOAD_SIZE_BYTES;
899    let mut chunk_idx = 1;
900    // Might be off by 1 on edge cases, but do we really care?
901    let total_chunks = payload.len() / MAX_LOG_PAYLOAD_SIZE_BYTES + 1;
902
903    while end < payload.len() {
904        // Find char boundary from the end.
905        // It's UTF-8, so it is within 4 bytes from here.
906        for _ in 0..4 {
907            if payload.is_char_boundary(end) {
908                break;
909            }
910            end -= 1;
911        }
912
913        log::info!(
914            "Glean ping to URL: {} [Part {} of {}]\n{}",
915            path,
916            chunk_idx,
917            total_chunks,
918            &payload[start..end]
919        );
920
921        // Move on with the string
922        start = end;
923        end = end + MAX_LOG_PAYLOAD_SIZE_BYTES;
924        chunk_idx += 1;
925    }
926
927    // Print any suffix left
928    if start < payload.len() {
929        log::info!(
930            "Glean ping to URL: {} [Part {} of {}]\n{}",
931            path,
932            chunk_idx,
933            total_chunks,
934            &payload[start..]
935        );
936    }
937}
938
939/// Logs payload in one go (all other OS).
940#[cfg(not(target_os = "android"))]
941pub fn chunked_log_info(_path: &str, payload: &str) {
942    log::info!("{}", payload)
943}
944
945#[cfg(test)]
946mod test {
947    use std::thread;
948    use uuid::Uuid;
949
950    use super::*;
951    use crate::metrics::PingType;
952    use crate::{tests::new_glean, PENDING_PINGS_DIRECTORY};
953
954    const PATH: &str = "/submit/app_id/ping_name/schema_version/doc_id";
955
956    #[test]
957    fn doesnt_error_when_there_are_no_pending_pings() {
958        let (glean, _t) = new_glean(None);
959
960        // Try and get the next request.
961        // Verify request was not returned
962        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
963    }
964
965    #[test]
966    fn returns_ping_request_when_there_is_one() {
967        let (glean, dir) = new_glean(None);
968
969        let upload_manager = PingUploadManager::no_policy(dir.path());
970
971        // Enqueue a ping
972        upload_manager.enqueue_ping(
973            &glean,
974            PingPayload {
975                document_id: Uuid::new_v4().to_string(),
976                upload_path: PATH.into(),
977                json_body: "".into(),
978                headers: None,
979                body_has_info_sections: true,
980                ping_name: "ping-name".into(),
981                uploader_capabilities: vec![],
982            },
983        );
984
985        // Try and get the next request.
986        // Verify request was returned
987        let task = upload_manager.get_upload_task(&glean, false);
988        assert!(task.is_upload());
989    }
990
991    #[test]
992    fn returns_as_many_ping_requests_as_there_are() {
993        let (glean, dir) = new_glean(None);
994
995        let upload_manager = PingUploadManager::no_policy(dir.path());
996
997        // Enqueue a ping multiple times
998        let n = 10;
999        for _ in 0..n {
1000            upload_manager.enqueue_ping(
1001                &glean,
1002                PingPayload {
1003                    document_id: Uuid::new_v4().to_string(),
1004                    upload_path: PATH.into(),
1005                    json_body: "".into(),
1006                    headers: None,
1007                    body_has_info_sections: true,
1008                    ping_name: "ping-name".into(),
1009                    uploader_capabilities: vec![],
1010                },
1011            );
1012        }
1013
1014        // Verify a request is returned for each submitted ping
1015        for _ in 0..n {
1016            let task = upload_manager.get_upload_task(&glean, false);
1017            assert!(task.is_upload());
1018        }
1019
1020        // Verify that after all requests are returned, none are left
1021        assert_eq!(
1022            upload_manager.get_upload_task(&glean, false),
1023            PingUploadTask::done()
1024        );
1025    }
1026
1027    #[test]
1028    fn limits_the_number_of_pings_when_there_is_rate_limiting() {
1029        let (glean, dir) = new_glean(None);
1030
1031        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1032
1033        // Add a rate limiter to the upload mangager with max of 10 pings every 3 seconds.
1034        let max_pings_per_interval = 10;
1035        upload_manager.set_rate_limiter(3, 10);
1036
1037        // Enqueue the max number of pings allowed per uploading window
1038        for _ in 0..max_pings_per_interval {
1039            upload_manager.enqueue_ping(
1040                &glean,
1041                PingPayload {
1042                    document_id: Uuid::new_v4().to_string(),
1043                    upload_path: PATH.into(),
1044                    json_body: "".into(),
1045                    headers: None,
1046                    body_has_info_sections: true,
1047                    ping_name: "ping-name".into(),
1048                    uploader_capabilities: vec![],
1049                },
1050            );
1051        }
1052
1053        // Verify a request is returned for each submitted ping
1054        for _ in 0..max_pings_per_interval {
1055            let task = upload_manager.get_upload_task(&glean, false);
1056            assert!(task.is_upload());
1057        }
1058
1059        // Enqueue just one more ping
1060        upload_manager.enqueue_ping(
1061            &glean,
1062            PingPayload {
1063                document_id: Uuid::new_v4().to_string(),
1064                upload_path: PATH.into(),
1065                json_body: "".into(),
1066                headers: None,
1067                body_has_info_sections: true,
1068                ping_name: "ping-name".into(),
1069                uploader_capabilities: vec![],
1070            },
1071        );
1072
1073        // Verify that we are indeed told to wait because we are at capacity
1074        match upload_manager.get_upload_task(&glean, false) {
1075            PingUploadTask::Wait { time } => {
1076                // Wait for the uploading window to reset
1077                thread::sleep(Duration::from_millis(time));
1078            }
1079            _ => panic!("Expected upload manager to return a wait task!"),
1080        };
1081
1082        let task = upload_manager.get_upload_task(&glean, false);
1083        assert!(task.is_upload());
1084    }
1085
1086    #[test]
1087    fn clearing_the_queue_works_correctly() {
1088        let (glean, dir) = new_glean(None);
1089
1090        let upload_manager = PingUploadManager::no_policy(dir.path());
1091
1092        // Enqueue a ping multiple times
1093        for _ in 0..10 {
1094            upload_manager.enqueue_ping(
1095                &glean,
1096                PingPayload {
1097                    document_id: Uuid::new_v4().to_string(),
1098                    upload_path: PATH.into(),
1099                    json_body: "".into(),
1100                    headers: None,
1101                    body_has_info_sections: true,
1102                    ping_name: "ping-name".into(),
1103                    uploader_capabilities: vec![],
1104                },
1105            );
1106        }
1107
1108        // Clear the queue
1109        drop(upload_manager.clear_ping_queue());
1110
1111        // Verify there really isn't any ping in the queue
1112        assert_eq!(
1113            upload_manager.get_upload_task(&glean, false),
1114            PingUploadTask::done()
1115        );
1116    }
1117
1118    #[test]
1119    fn clearing_the_queue_doesnt_clear_deletion_request_pings() {
1120        let (mut glean, _t) = new_glean(None);
1121
1122        // Register a ping for testing
1123        let ping_type = PingType::new(
1124            "test",
1125            true,
1126            /* send_if_empty */ true,
1127            true,
1128            true,
1129            true,
1130            vec![],
1131            vec![],
1132            true,
1133            vec![],
1134        );
1135        glean.register_ping_type(&ping_type);
1136
1137        // Submit the ping multiple times
1138        let n = 10;
1139        for _ in 0..n {
1140            ping_type.submit_sync(&glean, None);
1141        }
1142
1143        glean
1144            .internal_pings
1145            .deletion_request
1146            .submit_sync(&glean, None);
1147
1148        // Clear the queue
1149        drop(glean.upload_manager.clear_ping_queue());
1150
1151        let upload_task = glean.get_upload_task();
1152        match upload_task {
1153            PingUploadTask::Upload { request } => assert!(request.is_deletion_request()),
1154            _ => panic!("Expected upload manager to return the next request!"),
1155        }
1156
1157        // Verify there really isn't any other pings in the queue
1158        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1159    }
1160
1161    #[test]
1162    fn fills_up_queue_successfully_from_disk() {
1163        let (mut glean, dir) = new_glean(None);
1164
1165        // Register a ping for testing
1166        let ping_type = PingType::new(
1167            "test",
1168            true,
1169            /* send_if_empty */ true,
1170            true,
1171            true,
1172            true,
1173            vec![],
1174            vec![],
1175            true,
1176            vec![],
1177        );
1178        glean.register_ping_type(&ping_type);
1179
1180        // Submit the ping multiple times
1181        let n = 10;
1182        for _ in 0..n {
1183            ping_type.submit_sync(&glean, None);
1184        }
1185
1186        // Create a new upload manager pointing to the same data_path as the glean instance.
1187        let upload_manager = PingUploadManager::no_policy(dir.path());
1188
1189        // Verify the requests were properly enqueued
1190        for _ in 0..n {
1191            let task = upload_manager.get_upload_task(&glean, false);
1192            assert!(task.is_upload());
1193        }
1194
1195        // Verify that after all requests are returned, none are left
1196        assert_eq!(
1197            upload_manager.get_upload_task(&glean, false),
1198            PingUploadTask::done()
1199        );
1200    }
1201
1202    #[test]
1203    fn processes_correctly_success_upload_response() {
1204        let (mut glean, dir) = new_glean(None);
1205
1206        // Register a ping for testing
1207        let ping_type = PingType::new(
1208            "test",
1209            true,
1210            /* send_if_empty */ true,
1211            true,
1212            true,
1213            true,
1214            vec![],
1215            vec![],
1216            true,
1217            vec![],
1218        );
1219        glean.register_ping_type(&ping_type);
1220
1221        // Submit a ping
1222        ping_type.submit_sync(&glean, None);
1223
1224        // Get the pending ping directory path
1225        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1226
1227        // Get the submitted PingRequest
1228        match glean.get_upload_task() {
1229            PingUploadTask::Upload { request } => {
1230                // Simulate the processing of a sucessfull request
1231                let document_id = request.document_id;
1232                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
1233                // Verify file was deleted
1234                assert!(!pending_pings_dir.join(document_id).exists());
1235            }
1236            _ => panic!("Expected upload manager to return the next request!"),
1237        }
1238
1239        // Verify that after request is returned, none are left
1240        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1241    }
1242
1243    #[test]
1244    fn processes_correctly_client_error_upload_response() {
1245        let (mut glean, dir) = new_glean(None);
1246
1247        // Register a ping for testing
1248        let ping_type = PingType::new(
1249            "test",
1250            true,
1251            /* send_if_empty */ true,
1252            true,
1253            true,
1254            true,
1255            vec![],
1256            vec![],
1257            true,
1258            vec![],
1259        );
1260        glean.register_ping_type(&ping_type);
1261
1262        // Submit a ping
1263        ping_type.submit_sync(&glean, None);
1264
1265        // Get the pending ping directory path
1266        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1267
1268        // Get the submitted PingRequest
1269        match glean.get_upload_task() {
1270            PingUploadTask::Upload { request } => {
1271                // Simulate the processing of a client error
1272                let document_id = request.document_id;
1273                glean.process_ping_upload_response(&document_id, UploadResult::http_status(404));
1274                // Verify file was deleted
1275                assert!(!pending_pings_dir.join(document_id).exists());
1276            }
1277            _ => panic!("Expected upload manager to return the next request!"),
1278        }
1279
1280        // Verify that after request is returned, none are left
1281        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1282    }
1283
1284    #[test]
1285    fn processes_correctly_server_error_upload_response() {
1286        let (mut glean, _t) = new_glean(None);
1287
1288        // Register a ping for testing
1289        let ping_type = PingType::new(
1290            "test",
1291            true,
1292            /* send_if_empty */ true,
1293            true,
1294            true,
1295            true,
1296            vec![],
1297            vec![],
1298            true,
1299            vec![],
1300        );
1301        glean.register_ping_type(&ping_type);
1302
1303        // Submit a ping
1304        ping_type.submit_sync(&glean, None);
1305
1306        // Get the submitted PingRequest
1307        match glean.get_upload_task() {
1308            PingUploadTask::Upload { request } => {
1309                // Simulate the processing of a client error
1310                let document_id = request.document_id;
1311                glean.process_ping_upload_response(&document_id, UploadResult::http_status(500));
1312                // Verify this ping was indeed re-enqueued
1313                match glean.get_upload_task() {
1314                    PingUploadTask::Upload { request } => {
1315                        assert_eq!(document_id, request.document_id);
1316                    }
1317                    _ => panic!("Expected upload manager to return the next request!"),
1318                }
1319            }
1320            _ => panic!("Expected upload manager to return the next request!"),
1321        }
1322
1323        // Verify that after request is returned, none are left
1324        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1325    }
1326
1327    #[test]
1328    fn processes_correctly_unrecoverable_upload_response() {
1329        let (mut glean, dir) = new_glean(None);
1330
1331        // Register a ping for testing
1332        let ping_type = PingType::new(
1333            "test",
1334            true,
1335            /* send_if_empty */ true,
1336            true,
1337            true,
1338            true,
1339            vec![],
1340            vec![],
1341            true,
1342            vec![],
1343        );
1344        glean.register_ping_type(&ping_type);
1345
1346        // Submit a ping
1347        ping_type.submit_sync(&glean, None);
1348
1349        // Get the pending ping directory path
1350        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1351
1352        // Get the submitted PingRequest
1353        match glean.get_upload_task() {
1354            PingUploadTask::Upload { request } => {
1355                // Simulate the processing of a client error
1356                let document_id = request.document_id;
1357                glean.process_ping_upload_response(
1358                    &document_id,
1359                    UploadResult::unrecoverable_failure(),
1360                );
1361                // Verify file was deleted
1362                assert!(!pending_pings_dir.join(document_id).exists());
1363            }
1364            _ => panic!("Expected upload manager to return the next request!"),
1365        }
1366
1367        // Verify that after request is returned, none are left
1368        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1369    }
1370
1371    #[test]
1372    fn new_pings_are_added_while_upload_in_progress() {
1373        let (glean, dir) = new_glean(None);
1374
1375        let upload_manager = PingUploadManager::no_policy(dir.path());
1376
1377        let doc1 = Uuid::new_v4().to_string();
1378        let path1 = format!("/submit/app_id/test-ping/1/{}", doc1);
1379
1380        let doc2 = Uuid::new_v4().to_string();
1381        let path2 = format!("/submit/app_id/test-ping/1/{}", doc2);
1382
1383        // Enqueue a ping
1384        upload_manager.enqueue_ping(
1385            &glean,
1386            PingPayload {
1387                document_id: doc1.clone(),
1388                upload_path: path1,
1389                json_body: "".into(),
1390                headers: None,
1391                body_has_info_sections: true,
1392                ping_name: "test-ping".into(),
1393                uploader_capabilities: vec![],
1394            },
1395        );
1396
1397        // Try and get the first request.
1398        let req = match upload_manager.get_upload_task(&glean, false) {
1399            PingUploadTask::Upload { request } => request,
1400            _ => panic!("Expected upload manager to return the next request!"),
1401        };
1402        assert_eq!(doc1, req.document_id);
1403
1404        // Schedule the next one while the first one is "in progress"
1405        upload_manager.enqueue_ping(
1406            &glean,
1407            PingPayload {
1408                document_id: doc2.clone(),
1409                upload_path: path2,
1410                json_body: "".into(),
1411                headers: None,
1412                body_has_info_sections: true,
1413                ping_name: "test-ping".into(),
1414                uploader_capabilities: vec![],
1415            },
1416        );
1417
1418        // Mark as processed
1419        upload_manager.process_ping_upload_response(
1420            &glean,
1421            &req.document_id,
1422            UploadResult::http_status(200),
1423        );
1424
1425        // Get the second request.
1426        let req = match upload_manager.get_upload_task(&glean, false) {
1427            PingUploadTask::Upload { request } => request,
1428            _ => panic!("Expected upload manager to return the next request!"),
1429        };
1430        assert_eq!(doc2, req.document_id);
1431
1432        // Mark as processed
1433        upload_manager.process_ping_upload_response(
1434            &glean,
1435            &req.document_id,
1436            UploadResult::http_status(200),
1437        );
1438
1439        // ... and then we're done.
1440        assert_eq!(
1441            upload_manager.get_upload_task(&glean, false),
1442            PingUploadTask::done()
1443        );
1444    }
1445
1446    #[test]
1447    fn adds_debug_view_header_to_requests_when_tag_is_set() {
1448        let (mut glean, _t) = new_glean(None);
1449
1450        glean.set_debug_view_tag("valid-tag");
1451
1452        // Register a ping for testing
1453        let ping_type = PingType::new(
1454            "test",
1455            true,
1456            /* send_if_empty */ true,
1457            true,
1458            true,
1459            true,
1460            vec![],
1461            vec![],
1462            true,
1463            vec![],
1464        );
1465        glean.register_ping_type(&ping_type);
1466
1467        // Submit a ping
1468        ping_type.submit_sync(&glean, None);
1469
1470        // Get the submitted PingRequest
1471        match glean.get_upload_task() {
1472            PingUploadTask::Upload { request } => {
1473                assert_eq!(request.headers.get("X-Debug-ID").unwrap(), "valid-tag")
1474            }
1475            _ => panic!("Expected upload manager to return the next request!"),
1476        }
1477    }
1478
1479    #[test]
1480    fn duplicates_are_not_enqueued() {
1481        let (glean, dir) = new_glean(None);
1482
1483        // Create a new upload manager so that we have access to its functions directly,
1484        // make it synchronous so we don't have to manually wait for the scanning to finish.
1485        let upload_manager = PingUploadManager::no_policy(dir.path());
1486
1487        let doc_id = Uuid::new_v4().to_string();
1488        let path = format!("/submit/app_id/test-ping/1/{}", doc_id);
1489
1490        // Try to enqueue a ping with the same doc_id twice
1491        upload_manager.enqueue_ping(
1492            &glean,
1493            PingPayload {
1494                document_id: doc_id.clone(),
1495                upload_path: path.clone(),
1496                json_body: "".into(),
1497                headers: None,
1498                body_has_info_sections: true,
1499                ping_name: "test-ping".into(),
1500                uploader_capabilities: vec![],
1501            },
1502        );
1503        upload_manager.enqueue_ping(
1504            &glean,
1505            PingPayload {
1506                document_id: doc_id,
1507                upload_path: path,
1508                json_body: "".into(),
1509                headers: None,
1510                body_has_info_sections: true,
1511                ping_name: "test-ping".into(),
1512                uploader_capabilities: vec![],
1513            },
1514        );
1515
1516        // Get a task once
1517        let task = upload_manager.get_upload_task(&glean, false);
1518        assert!(task.is_upload());
1519
1520        // There should be no more queued tasks
1521        assert_eq!(
1522            upload_manager.get_upload_task(&glean, false),
1523            PingUploadTask::done()
1524        );
1525    }
1526
1527    #[test]
1528    fn maximum_of_recoverable_errors_is_enforced_for_uploading_window() {
1529        let (mut glean, dir) = new_glean(None);
1530
1531        // Register a ping for testing
1532        let ping_type = PingType::new(
1533            "test",
1534            true,
1535            /* send_if_empty */ true,
1536            true,
1537            true,
1538            true,
1539            vec![],
1540            vec![],
1541            true,
1542            vec![],
1543        );
1544        glean.register_ping_type(&ping_type);
1545
1546        // Submit the ping multiple times
1547        let n = 5;
1548        for _ in 0..n {
1549            ping_type.submit_sync(&glean, None);
1550        }
1551
1552        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1553
1554        // Set a policy for max recoverable failures, this is usually disabled for tests.
1555        let max_recoverable_failures = 3;
1556        upload_manager
1557            .policy
1558            .set_max_recoverable_failures(Some(max_recoverable_failures));
1559
1560        // Return the max recoverable error failures in a row
1561        for _ in 0..max_recoverable_failures {
1562            match upload_manager.get_upload_task(&glean, false) {
1563                PingUploadTask::Upload { request } => {
1564                    upload_manager.process_ping_upload_response(
1565                        &glean,
1566                        &request.document_id,
1567                        UploadResult::recoverable_failure(),
1568                    );
1569                }
1570                _ => panic!("Expected upload manager to return the next request!"),
1571            }
1572        }
1573
1574        // Verify that after returning the max amount of recoverable failures,
1575        // we are done even though we haven't gotten all the enqueued requests.
1576        assert_eq!(
1577            upload_manager.get_upload_task(&glean, false),
1578            PingUploadTask::done()
1579        );
1580
1581        // Verify all requests are returned when we try again.
1582        for _ in 0..n {
1583            let task = upload_manager.get_upload_task(&glean, false);
1584            assert!(task.is_upload());
1585        }
1586    }
1587
1588    #[test]
1589    fn quota_is_enforced_when_enqueueing_cached_pings() {
1590        let (mut glean, dir) = new_glean(None);
1591
1592        // Register a ping for testing
1593        let ping_type = PingType::new(
1594            "test",
1595            true,
1596            /* send_if_empty */ true,
1597            true,
1598            true,
1599            true,
1600            vec![],
1601            vec![],
1602            true,
1603            vec![],
1604        );
1605        glean.register_ping_type(&ping_type);
1606
1607        // Submit the ping multiple times
1608        let n = 10;
1609        for _ in 0..n {
1610            ping_type.submit_sync(&glean, None);
1611        }
1612
1613        let directory_manager = PingDirectoryManager::new(dir.path());
1614        let pending_pings = directory_manager.process_dirs().pending_pings;
1615        // The pending pings array is sorted by date in ascending order,
1616        // the newest element is the last one.
1617        let (_, newest_ping) = &pending_pings.last().unwrap();
1618        let PingPayload {
1619            document_id: newest_ping_id,
1620            ..
1621        } = &newest_ping;
1622
1623        // Create a new upload manager pointing to the same data_path as the glean instance.
1624        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1625
1626        // Set the quota to just a little over the size on an empty ping file.
1627        // This way we can check that one ping is kept and all others are deleted.
1628        //
1629        // From manual testing I figured out an empty ping file is 324bytes,
1630        // I am setting this a little over just so that minor changes to the ping structure
1631        // don't immediatelly break this.
1632        upload_manager
1633            .policy
1634            .set_max_pending_pings_directory_size(Some(500));
1635
1636        // Get a task once
1637        // One ping should have been enqueued.
1638        // Make sure it is the newest ping.
1639        match upload_manager.get_upload_task(&glean, false) {
1640            PingUploadTask::Upload { request } => assert_eq!(&request.document_id, newest_ping_id),
1641            _ => panic!("Expected upload manager to return the next request!"),
1642        }
1643
1644        // Verify that no other requests were returned,
1645        // they should all have been deleted because pending pings quota was hit.
1646        assert_eq!(
1647            upload_manager.get_upload_task(&glean, false),
1648            PingUploadTask::done()
1649        );
1650
1651        // Verify that the correct number of deleted pings was recorded
1652        assert_eq!(
1653            n - 1,
1654            upload_manager
1655                .upload_metrics
1656                .deleted_pings_after_quota_hit
1657                .get_value(&glean, Some("metrics"))
1658                .unwrap()
1659        );
1660        assert_eq!(
1661            n,
1662            upload_manager
1663                .upload_metrics
1664                .pending_pings
1665                .get_value(&glean, Some("metrics"))
1666                .unwrap()
1667        );
1668    }
1669
1670    #[test]
1671    fn number_quota_is_enforced_when_enqueueing_cached_pings() {
1672        let (mut glean, dir) = new_glean(None);
1673
1674        // Register a ping for testing
1675        let ping_type = PingType::new(
1676            "test",
1677            true,
1678            /* send_if_empty */ true,
1679            true,
1680            true,
1681            true,
1682            vec![],
1683            vec![],
1684            true,
1685            vec![],
1686        );
1687        glean.register_ping_type(&ping_type);
1688
1689        // How many pings we allow at maximum
1690        let count_quota = 3;
1691        // The number of pings we fill the pending pings directory with.
1692        let n = 10;
1693
1694        // Submit the ping multiple times
1695        for _ in 0..n {
1696            ping_type.submit_sync(&glean, None);
1697        }
1698
1699        let directory_manager = PingDirectoryManager::new(dir.path());
1700        let pending_pings = directory_manager.process_dirs().pending_pings;
1701        // The pending pings array is sorted by date in ascending order,
1702        // the newest element is the last one.
1703        let expected_pings = pending_pings
1704            .iter()
1705            .rev()
1706            .take(count_quota)
1707            .map(|(_, ping)| ping.document_id.clone())
1708            .collect::<Vec<_>>();
1709
1710        // Create a new upload manager pointing to the same data_path as the glean instance.
1711        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1712
1713        upload_manager
1714            .policy
1715            .set_max_pending_pings_count(Some(count_quota as u64));
1716
1717        // Get a task once
1718        // One ping should have been enqueued.
1719        // Make sure it is the newest ping.
1720        for ping_id in expected_pings.iter().rev() {
1721            match upload_manager.get_upload_task(&glean, false) {
1722                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1723                _ => panic!("Expected upload manager to return the next request!"),
1724            }
1725        }
1726
1727        // Verify that no other requests were returned,
1728        // they should all have been deleted because pending pings quota was hit.
1729        assert_eq!(
1730            upload_manager.get_upload_task(&glean, false),
1731            PingUploadTask::done()
1732        );
1733
1734        // Verify that the correct number of deleted pings was recorded
1735        assert_eq!(
1736            (n - count_quota) as i32,
1737            upload_manager
1738                .upload_metrics
1739                .deleted_pings_after_quota_hit
1740                .get_value(&glean, Some("metrics"))
1741                .unwrap()
1742        );
1743        assert_eq!(
1744            n as i32,
1745            upload_manager
1746                .upload_metrics
1747                .pending_pings
1748                .get_value(&glean, Some("metrics"))
1749                .unwrap()
1750        );
1751    }
1752
1753    #[test]
1754    fn size_and_count_quota_work_together_size_first() {
1755        let (mut glean, dir) = new_glean(None);
1756
1757        // Register a ping for testing
1758        let ping_type = PingType::new(
1759            "test",
1760            true,
1761            /* send_if_empty */ true,
1762            true,
1763            true,
1764            true,
1765            vec![],
1766            vec![],
1767            true,
1768            vec![],
1769        );
1770        glean.register_ping_type(&ping_type);
1771
1772        let expected_number_of_pings = 3;
1773        // The number of pings we fill the pending pings directory with.
1774        let n = 10;
1775
1776        // Submit the ping multiple times
1777        for _ in 0..n {
1778            ping_type.submit_sync(&glean, None);
1779        }
1780
1781        let directory_manager = PingDirectoryManager::new(dir.path());
1782        let pending_pings = directory_manager.process_dirs().pending_pings;
1783        // The pending pings array is sorted by date in ascending order,
1784        // the newest element is the last one.
1785        let expected_pings = pending_pings
1786            .iter()
1787            .rev()
1788            .take(expected_number_of_pings)
1789            .map(|(_, ping)| ping.document_id.clone())
1790            .collect::<Vec<_>>();
1791
1792        // Create a new upload manager pointing to the same data_path as the glean instance.
1793        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1794
1795        // From manual testing we figured out a basically empty ping file is 399 bytes,
1796        // so this allows 3 pings with some headroom in case of future changes.
1797        upload_manager
1798            .policy
1799            .set_max_pending_pings_directory_size(Some(1300));
1800        upload_manager.policy.set_max_pending_pings_count(Some(5));
1801
1802        // Get a task once
1803        // One ping should have been enqueued.
1804        // Make sure it is the newest ping.
1805        for ping_id in expected_pings.iter().rev() {
1806            match upload_manager.get_upload_task(&glean, false) {
1807                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1808                _ => panic!("Expected upload manager to return the next request!"),
1809            }
1810        }
1811
1812        // Verify that no other requests were returned,
1813        // they should all have been deleted because pending pings quota was hit.
1814        assert_eq!(
1815            upload_manager.get_upload_task(&glean, false),
1816            PingUploadTask::done()
1817        );
1818
1819        // Verify that the correct number of deleted pings was recorded
1820        assert_eq!(
1821            (n - expected_number_of_pings) as i32,
1822            upload_manager
1823                .upload_metrics
1824                .deleted_pings_after_quota_hit
1825                .get_value(&glean, Some("metrics"))
1826                .unwrap()
1827        );
1828        assert_eq!(
1829            n as i32,
1830            upload_manager
1831                .upload_metrics
1832                .pending_pings
1833                .get_value(&glean, Some("metrics"))
1834                .unwrap()
1835        );
1836        // Verify the labeled deletion counter attributes deletions to size_quota
1837        assert_eq!(
1838            (n - expected_number_of_pings) as i32,
1839            upload_manager
1840                .upload_metrics
1841                .pending_pings_deleted
1842                .get("size_quota")
1843                .get_value(&glean, Some("health"))
1844                .unwrap()
1845        );
1846        assert!(upload_manager
1847            .upload_metrics
1848            .pending_pings_deleted
1849            .get("count_quota")
1850            .get_value(&glean, Some("health"))
1851            .is_none());
1852    }
1853
1854    #[test]
1855    fn size_and_count_quota_work_together_count_first() {
1856        let (mut glean, dir) = new_glean(None);
1857
1858        // Register a ping for testing
1859        let ping_type = PingType::new(
1860            "test",
1861            true,
1862            /* send_if_empty */ true,
1863            true,
1864            true,
1865            true,
1866            vec![],
1867            vec![],
1868            true,
1869            vec![],
1870        );
1871        glean.register_ping_type(&ping_type);
1872
1873        let expected_number_of_pings = 2;
1874        // The number of pings we fill the pending pings directory with.
1875        let n = 10;
1876
1877        // Submit the ping multiple times
1878        for _ in 0..n {
1879            ping_type.submit_sync(&glean, None);
1880        }
1881
1882        let directory_manager = PingDirectoryManager::new(dir.path());
1883        let pending_pings = directory_manager.process_dirs().pending_pings;
1884        // The pending pings array is sorted by date in ascending order,
1885        // the newest element is the last one.
1886        let expected_pings = pending_pings
1887            .iter()
1888            .rev()
1889            .take(expected_number_of_pings)
1890            .map(|(_, ping)| ping.document_id.clone())
1891            .collect::<Vec<_>>();
1892
1893        // Create a new upload manager pointing to the same data_path as the glean instance.
1894        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1895
1896        // Set a large enough size quota so it never triggers before the count quota does.
1897        upload_manager
1898            .policy
1899            .set_max_pending_pings_directory_size(Some(100_000));
1900        upload_manager.policy.set_max_pending_pings_count(Some(2));
1901
1902        // Get a task once
1903        // One ping should have been enqueued.
1904        // Make sure it is the newest ping.
1905        for ping_id in expected_pings.iter().rev() {
1906            match upload_manager.get_upload_task(&glean, false) {
1907                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1908                _ => panic!("Expected upload manager to return the next request!"),
1909            }
1910        }
1911
1912        // Verify that no other requests were returned,
1913        // they should all have been deleted because pending pings quota was hit.
1914        assert_eq!(
1915            upload_manager.get_upload_task(&glean, false),
1916            PingUploadTask::done()
1917        );
1918
1919        // Verify that the correct number of deleted pings was recorded
1920        assert_eq!(
1921            (n - expected_number_of_pings) as i32,
1922            upload_manager
1923                .upload_metrics
1924                .deleted_pings_after_quota_hit
1925                .get_value(&glean, Some("metrics"))
1926                .unwrap()
1927        );
1928        assert_eq!(
1929            n as i32,
1930            upload_manager
1931                .upload_metrics
1932                .pending_pings
1933                .get_value(&glean, Some("metrics"))
1934                .unwrap()
1935        );
1936        // Verify the labeled deletion counter attributes deletions to count_quota
1937        assert_eq!(
1938            (n - expected_number_of_pings) as i32,
1939            upload_manager
1940                .upload_metrics
1941                .pending_pings_deleted
1942                .get("count_quota")
1943                .get_value(&glean, Some("health"))
1944                .unwrap()
1945        );
1946        assert!(upload_manager
1947            .upload_metrics
1948            .pending_pings_deleted
1949            .get("size_quota")
1950            .get_value(&glean, Some("health"))
1951            .is_none());
1952    }
1953
1954    #[test]
1955    fn pending_pings_deleted_is_not_recorded_when_quota_not_hit() {
1956        let (mut glean, dir) = new_glean(None);
1957
1958        let ping_type = PingType::new(
1959            "test",
1960            true,
1961            /* send_if_empty */ true,
1962            true,
1963            true,
1964            true,
1965            vec![],
1966            vec![],
1967            true,
1968            vec![],
1969        );
1970        glean.register_ping_type(&ping_type);
1971
1972        // Submit fewer pings than any quota.
1973        for _ in 0..3 {
1974            ping_type.submit_sync(&glean, None);
1975        }
1976
1977        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1978        upload_manager.policy.set_max_pending_pings_count(Some(10));
1979        upload_manager
1980            .policy
1981            .set_max_pending_pings_directory_size(Some(1024 * 1024));
1982
1983        upload_manager.get_upload_task(&glean, false);
1984
1985        assert!(upload_manager
1986            .upload_metrics
1987            .pending_pings_deleted
1988            .get("count_quota")
1989            .get_value(&glean, Some("health"))
1990            .is_none());
1991        assert!(upload_manager
1992            .upload_metrics
1993            .pending_pings_deleted
1994            .get("size_quota")
1995            .get_value(&glean, Some("health"))
1996            .is_none());
1997    }
1998
1999    #[test]
2000    fn pending_pings_config_overrides_are_applied() {
2001        let (_, dir) = new_glean(None);
2002
2003        let mut upload_manager = PingUploadManager::new(dir.path(), "test");
2004
2005        let custom_count: u64 = 42;
2006        let custom_size: u64 = 999_999;
2007        upload_manager.set_max_pending_pings_count(custom_count);
2008        upload_manager.set_max_pending_pings_directory_size(custom_size);
2009
2010        assert_eq!(
2011            custom_count,
2012            upload_manager.policy.max_pending_pings_count()
2013        );
2014        assert_eq!(
2015            custom_size,
2016            upload_manager.policy.max_pending_pings_directory_size()
2017        );
2018    }
2019
2020    #[test]
2021    fn maximum_wait_attemps_is_enforced() {
2022        let (glean, dir) = new_glean(None);
2023
2024        let mut upload_manager = PingUploadManager::no_policy(dir.path());
2025
2026        // Define a max_wait_attemps policy, this is disabled for tests by default.
2027        let max_wait_attempts = 3;
2028        upload_manager
2029            .policy
2030            .set_max_wait_attempts(Some(max_wait_attempts));
2031
2032        // Add a rate limiter to the upload mangager with max of 1 ping 5secs.
2033        //
2034        // We arbitrarily set the maximum pings per interval to a very low number,
2035        // when the rate limiter reaches it's limit get_upload_task returns a PingUploadTask::Wait,
2036        // which will allow us to test the limitations around returning too many of those in a row.
2037        let secs_per_interval = 5;
2038        let max_pings_per_interval = 1;
2039        upload_manager.set_rate_limiter(secs_per_interval, max_pings_per_interval);
2040
2041        // Enqueue two pings
2042        upload_manager.enqueue_ping(
2043            &glean,
2044            PingPayload {
2045                document_id: Uuid::new_v4().to_string(),
2046                upload_path: PATH.into(),
2047                json_body: "".into(),
2048                headers: None,
2049                body_has_info_sections: true,
2050                ping_name: "ping-name".into(),
2051                uploader_capabilities: vec![],
2052            },
2053        );
2054        upload_manager.enqueue_ping(
2055            &glean,
2056            PingPayload {
2057                document_id: Uuid::new_v4().to_string(),
2058                upload_path: PATH.into(),
2059                json_body: "".into(),
2060                headers: None,
2061                body_has_info_sections: true,
2062                ping_name: "ping-name".into(),
2063                uploader_capabilities: vec![],
2064            },
2065        );
2066
2067        // Get the first ping, it should be returned normally.
2068        match upload_manager.get_upload_task(&glean, false) {
2069            PingUploadTask::Upload { .. } => {}
2070            _ => panic!("Expected upload manager to return the next request!"),
2071        }
2072
2073        // Try to get the next ping,
2074        // we should be throttled and thus get a PingUploadTask::Wait.
2075        // Check that we are indeed allowed to get this response as many times as expected.
2076        for _ in 0..max_wait_attempts {
2077            let task = upload_manager.get_upload_task(&glean, false);
2078            assert!(task.is_wait());
2079        }
2080
2081        // Check that after we get PingUploadTask::Wait the allowed number of times,
2082        // we then get PingUploadTask::Done.
2083        assert_eq!(
2084            upload_manager.get_upload_task(&glean, false),
2085            PingUploadTask::done()
2086        );
2087
2088        // Wait for the rate limiter to allow upload tasks again.
2089        thread::sleep(Duration::from_secs(secs_per_interval));
2090
2091        // Check that we are allowed again to get pings.
2092        let task = upload_manager.get_upload_task(&glean, false);
2093        assert!(task.is_upload());
2094
2095        // And once we are done we don't need to wait anymore.
2096        assert_eq!(
2097            upload_manager.get_upload_task(&glean, false),
2098            PingUploadTask::done()
2099        );
2100    }
2101
2102    #[test]
2103    fn wait_task_contains_expected_wait_time_when_pending_pings_dir_not_processed_yet() {
2104        let (glean, dir) = new_glean(None);
2105        let upload_manager = PingUploadManager::new(dir.path(), "test");
2106        match upload_manager.get_upload_task(&glean, false) {
2107            PingUploadTask::Wait { time } => {
2108                assert_eq!(time, WAIT_TIME_FOR_PING_PROCESSING);
2109            }
2110            _ => panic!("Expected upload manager to return a wait task!"),
2111        };
2112    }
2113
2114    #[test]
2115    fn cannot_enqueue_ping_while_its_being_processed() {
2116        let (glean, dir) = new_glean(None);
2117
2118        let upload_manager = PingUploadManager::no_policy(dir.path());
2119
2120        // Enqueue a ping and start processing it
2121        let identifier = &Uuid::new_v4();
2122        let ping = PingPayload {
2123            document_id: identifier.to_string(),
2124            upload_path: PATH.into(),
2125            json_body: "".into(),
2126            headers: None,
2127            body_has_info_sections: true,
2128            ping_name: "ping-name".into(),
2129            uploader_capabilities: vec![],
2130        };
2131        upload_manager.enqueue_ping(&glean, ping);
2132        assert!(upload_manager.get_upload_task(&glean, false).is_upload());
2133
2134        // Attempt to re-enqueue the same ping
2135        let ping = PingPayload {
2136            document_id: identifier.to_string(),
2137            upload_path: PATH.into(),
2138            json_body: "".into(),
2139            headers: None,
2140            body_has_info_sections: true,
2141            ping_name: "ping-name".into(),
2142            uploader_capabilities: vec![],
2143        };
2144        upload_manager.enqueue_ping(&glean, ping);
2145
2146        // No new pings should have been enqueued so the upload task is Done.
2147        assert_eq!(
2148            upload_manager.get_upload_task(&glean, false),
2149            PingUploadTask::done()
2150        );
2151
2152        // Process the upload response
2153        upload_manager.process_ping_upload_response(
2154            &glean,
2155            &identifier.to_string(),
2156            UploadResult::http_status(200),
2157        );
2158    }
2159
2160    #[test]
2161    fn stores_pings_during_submission_and_upload_if_enabled() {
2162        let (mut glean, _t) = new_glean(None);
2163        glean.set_store_submitted_pings_enabled(true);
2164
2165        // Register a ping for testing
2166        let ping_type = PingType::new(
2167            "test",
2168            true,
2169            /* send_if_empty */ true,
2170            true,
2171            true,
2172            true,
2173            vec![],
2174            vec![],
2175            true,
2176            vec![],
2177        );
2178        glean.register_ping_type(&ping_type);
2179
2180        // Submit a ping
2181        ping_type.submit_sync(&glean, None);
2182
2183        let pings = glean.storage().get_all_submitted_pings();
2184        assert_eq!(pings.len(), 1);
2185        let ping = pings.first().unwrap();
2186        assert!(ping.submitted_date() <= Utc::now());
2187        assert!(ping.uploaded_date.is_none());
2188
2189        // Get the submitted PingRequest
2190        match glean.get_upload_task() {
2191            PingUploadTask::Upload { request } => {
2192                // Simulate the processing of a sucessful request
2193                let document_id = request.document_id;
2194                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
2195            }
2196            _ => panic!("Expected upload manager to return the next request!"),
2197        }
2198
2199        let pings = glean.storage().get_all_submitted_pings();
2200        assert_eq!(pings.len(), 1);
2201        let ping = pings.first().unwrap();
2202        assert!(ping.submitted_date() <= Utc::now());
2203        assert!(ping.uploaded_date.is_some());
2204
2205        // Verify that after request is returned, none are left
2206        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2207    }
2208
2209    #[test]
2210    fn stores_pings_during_submission_and_marks_as_upload_failed_when_appropriate() {
2211        let (mut glean, _t) = new_glean(None);
2212        glean.set_store_submitted_pings_enabled(true);
2213
2214        // Register a ping for testing
2215        let ping_type = PingType::new(
2216            "test",
2217            true,
2218            /* send_if_empty */ true,
2219            true,
2220            true,
2221            true,
2222            vec![],
2223            vec![],
2224            true,
2225            vec![],
2226        );
2227        glean.register_ping_type(&ping_type);
2228
2229        // Submit a ping
2230        ping_type.submit_sync(&glean, None);
2231
2232        let pings = glean.storage().get_all_submitted_pings();
2233        assert_eq!(pings.len(), 1);
2234        let ping = pings.first().unwrap();
2235        assert!(ping.submitted_date() <= Utc::now());
2236        assert!(ping.uploaded_date.is_none());
2237
2238        // Get the submitted PingRequest
2239        match glean.get_upload_task() {
2240            PingUploadTask::Upload { request } => {
2241                // Simulate the processing of a sucessful request
2242                let document_id = request.document_id;
2243                glean.process_ping_upload_response(&document_id, UploadResult::http_status(400));
2244            }
2245            _ => panic!("Expected upload manager to return the next request!"),
2246        }
2247
2248        let pings = glean.storage().get_all_submitted_pings();
2249        assert_eq!(pings.len(), 1);
2250        let ping = pings.first().unwrap();
2251        assert!(ping.submitted_date() <= Utc::now());
2252        assert!(ping.upload_failed.is_some());
2253        assert!(ping.uploaded_date.is_none());
2254
2255        // Verify that after request is returned, none are left
2256        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2257    }
2258}