{"version":3,"file":"sendWithRetryStrategy.mjs","names":[],"sources":["../../src/transport/sendWithRetryStrategy.ts"],"sourcesContent":["import { ONE_MINUTE, ONE_SECOND, clocksNow } from '@openobserve/js-core/time'\nimport type { TrackType } from '../domain/configuration'\nimport { setTimeout } from '../tools/timer'\nimport { ONE_MEBI_BYTE, ONE_KIBI_BYTE } from '../tools/utils/byteUtils'\nimport { isServerError } from '../tools/utils/responseUtils'\nimport type { RawError } from '../domain/error/error.types'\nimport { ErrorSource } from '../domain/error/error.types'\nimport type { Observable } from '../tools/observable'\nimport type { Payload, HttpRequestEvent, HttpResponse, BandwidthStats } from './httpRequest'\n\nexport const MAX_ONGOING_BYTES_COUNT = 80 * ONE_KIBI_BYTE\nexport const MAX_ONGOING_REQUESTS = 32\nexport const MAX_QUEUE_BYTES_COUNT = 20 * ONE_MEBI_BYTE\nexport const MAX_BACKOFF_TIME = ONE_MINUTE\nexport const INITIAL_BACKOFF_TIME = ONE_SECOND\n\nconst enum TransportStatus {\n  UP,\n  FAILURE_DETECTED,\n  DOWN,\n}\n\nconst enum RetryReason {\n  AFTER_SUCCESS,\n  AFTER_RESUME,\n}\n\nexport interface RetryState<Body extends Payload> {\n  transportStatus: TransportStatus\n  currentBackoffTime: number\n  bandwidthMonitor: ReturnType<typeof newBandwidthMonitor>\n  queuedPayloads: ReturnType<typeof newPayloadQueue<Body>>\n  queueFullReported: boolean\n}\n\ntype SendStrategy<Body extends Payload> = (payload: Body, onResponse: (r: HttpResponse) => void) => void\n\nexport function sendWithRetryStrategy<Body extends Payload>(\n  payload: Body,\n  state: RetryState<Body>,\n  sendStrategy: SendStrategy<Body>,\n  trackType: TrackType,\n  reportError: (error: RawError) => void,\n  requestObservable: Observable<HttpRequestEvent<Body>>\n) {\n  if (\n    state.transportStatus === TransportStatus.UP &&\n    state.queuedPayloads.size() === 0 &&\n    state.bandwidthMonitor.canHandle(payload)\n  ) {\n    send(payload, state, sendStrategy, requestObservable, {\n      onSuccess: () =>\n        retryQueuedPayloads(RetryReason.AFTER_SUCCESS, state, sendStrategy, trackType, reportError, requestObservable),\n      onFailure: () => {\n        if (!state.queuedPayloads.enqueue(payload)) {\n          requestObservable.notify({ type: 'queue-full', bandwidth: state.bandwidthMonitor.stats(), payload })\n        }\n        scheduleRetry(state, sendStrategy, trackType, reportError, requestObservable)\n      },\n    })\n  } else {\n    if (!state.queuedPayloads.enqueue(payload)) {\n      requestObservable.notify({ type: 'queue-full', bandwidth: state.bandwidthMonitor.stats(), payload })\n    }\n  }\n}\n\nfunction scheduleRetry<Body extends Payload>(\n  state: RetryState<Body>,\n  sendStrategy: SendStrategy<Body>,\n  trackType: TrackType,\n  reportError: (error: RawError) => void,\n  requestObservable: Observable<HttpRequestEvent<Body>>\n) {\n  if (state.transportStatus !== TransportStatus.DOWN) {\n    return\n  }\n  setTimeout(() => {\n    const payload = state.queuedPayloads.first()\n    send(payload, state, sendStrategy, requestObservable, {\n      onSuccess: () => {\n        state.queuedPayloads.dequeue()\n        state.currentBackoffTime = INITIAL_BACKOFF_TIME\n        retryQueuedPayloads(RetryReason.AFTER_RESUME, state, sendStrategy, trackType, reportError, requestObservable)\n      },\n      onFailure: () => {\n        state.currentBackoffTime = Math.min(MAX_BACKOFF_TIME, state.currentBackoffTime * 2)\n        scheduleRetry(state, sendStrategy, trackType, reportError, requestObservable)\n      },\n    })\n  }, state.currentBackoffTime)\n}\n\nfunction send<Body extends Payload>(\n  payload: Body,\n  state: RetryState<Body>,\n  sendStrategy: SendStrategy<Body>,\n  requestObservable: Observable<HttpRequestEvent<Body>>,\n  { onSuccess, onFailure }: { onSuccess: () => void; onFailure: () => void }\n) {\n  state.bandwidthMonitor.add(payload)\n  sendStrategy(payload, (response) => {\n    state.bandwidthMonitor.remove(payload)\n    if (!shouldRetryRequest(response)) {\n      state.transportStatus = TransportStatus.UP\n      requestObservable.notify({ type: 'success', bandwidth: state.bandwidthMonitor.stats(), payload })\n      onSuccess()\n    } else {\n      // do not consider transport down if another ongoing request could succeed\n      state.transportStatus =\n        state.bandwidthMonitor.ongoingRequestCount > 0 ? TransportStatus.FAILURE_DETECTED : TransportStatus.DOWN\n      payload.retry = {\n        count: payload.retry ? payload.retry.count + 1 : 1,\n        lastFailureStatus: response.status,\n      }\n      requestObservable.notify({ type: 'failure', bandwidth: state.bandwidthMonitor.stats(), payload })\n      onFailure()\n    }\n  })\n}\n\nfunction retryQueuedPayloads<Body extends Payload>(\n  reason: RetryReason,\n  state: RetryState<Body>,\n  sendStrategy: SendStrategy<Body>,\n  trackType: TrackType,\n  reportError: (error: RawError) => void,\n  requestObservable: Observable<HttpRequestEvent<Body>>\n) {\n  if (reason === RetryReason.AFTER_SUCCESS && state.queuedPayloads.isFull() && !state.queueFullReported) {\n    reportError({\n      message: `Reached max ${trackType} events size queued for upload: ${MAX_QUEUE_BYTES_COUNT / ONE_MEBI_BYTE}MiB`,\n      source: ErrorSource.AGENT,\n      startClocks: clocksNow(),\n    })\n    state.queueFullReported = true\n  }\n  const previousQueue = state.queuedPayloads\n  state.queuedPayloads = newPayloadQueue()\n  while (previousQueue.size() > 0) {\n    sendWithRetryStrategy(previousQueue.dequeue()!, state, sendStrategy, trackType, reportError, requestObservable)\n  }\n}\n\nfunction shouldRetryRequest(response: HttpResponse) {\n  return (\n    response.type !== 'opaque' &&\n    ((response.status === 0 && !navigator.onLine) ||\n      response.status === 408 ||\n      response.status === 429 ||\n      isServerError(response.status))\n  )\n}\n\nexport function newRetryState<Body extends Payload>(): RetryState<Body> {\n  return {\n    transportStatus: TransportStatus.UP,\n    currentBackoffTime: INITIAL_BACKOFF_TIME,\n    bandwidthMonitor: newBandwidthMonitor(),\n    queuedPayloads: newPayloadQueue(),\n    queueFullReported: false,\n  }\n}\n\nfunction newPayloadQueue<Body extends Payload>() {\n  const queue: Body[] = []\n  return {\n    bytesCount: 0,\n    enqueue(payload: Body) {\n      if (this.isFull()) {\n        return false\n      }\n      queue.push(payload)\n      this.bytesCount += payload.bytesCount\n      return true\n    },\n    first() {\n      return queue[0]\n    },\n    dequeue() {\n      const payload = queue.shift()\n      if (payload) {\n        this.bytesCount -= payload.bytesCount\n      }\n      return payload\n    },\n    size() {\n      return queue.length\n    },\n    isFull() {\n      return this.bytesCount >= MAX_QUEUE_BYTES_COUNT\n    },\n  }\n}\n\nfunction newBandwidthMonitor() {\n  return {\n    ongoingRequestCount: 0,\n    ongoingByteCount: 0,\n    canHandle(payload: Payload) {\n      return (\n        this.ongoingRequestCount === 0 ||\n        (this.ongoingByteCount + payload.bytesCount <= MAX_ONGOING_BYTES_COUNT &&\n          this.ongoingRequestCount < MAX_ONGOING_REQUESTS)\n      )\n    },\n    add(payload: Payload) {\n      this.ongoingRequestCount += 1\n      this.ongoingByteCount += payload.bytesCount\n    },\n    remove(payload: Payload) {\n      this.ongoingRequestCount -= 1\n      this.ongoingByteCount -= payload.bytesCount\n    },\n    stats(): BandwidthStats {\n      return {\n        ongoingByteCount: this.ongoingByteCount,\n        ongoingRequestCount: this.ongoingRequestCount,\n      }\n    },\n  }\n}\n"],"mappings":"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"}