transistorsoft / react-native-background-geolocation

Sophisticated, battery-conscious background-geolocation with motion-detection
http://shop.transistorsoft.com/pages/react-native-background-geolocation
MIT License
2.63k stars 425 forks source link

[Question] How to know when batch syncing is busy. #2165

Open louwjlabuschagne opened 3 days ago

louwjlabuschagne commented 3 days ago

Summary

We are encountering a HTTPService is busy error when attempting to call our custom endpoint that utilizes axios. This error occurs because background syncing (handled by the background-geolocation library) is still processing while we attempt to make the call. We are looking for a way to detect when the batch syncing process is in progress—ideally by subscribing to an isLoading state or some other observable mechanism. This would allow us to handle the API call more effectively and avoid conflicts with ongoing sync operations.

Your Environment

Plugin Configuration

Here’s our current configuration setup for the background geolocation service:

await BackgroundGeolocation.ready({
  httpRootProperty: ".",
  locationTemplate:
    '{"age":"<%= age %>","accuracy":"<%= accuracy %>","longitude":"<%= longitude %>","latitude":"<%= latitude %>","timestamp":"<%= timestamp %>","batteryLevel":"<%= battery.level %>","odometer":"<%= odometer %>"}',
  reset: true,
  debug: false,
  logLevel: BackgroundGeolocation.LOG_LEVEL_OFF,
  desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_NAVIGATION,
  distanceFilter: Platform.OS === "android" ? 0 : 5,
  locationUpdateInterval: 1000,
  fastestLocationUpdateInterval: 10000,
  autoSync: true,
  autoSyncThreshold: 50,
  batchSync: true,
  maxDaysToPersist: 14,
  maxBatchSize: 500,
  enableHeadless: true,
  disableLocationAuthorizationAlert: true,
  disableElasticity: true,
  pausesLocationUpdatesAutomatically: false,
  disableMotionActivityUpdates: true,
  heartbeatInterval: 60,
  stopOnTerminate: Platform.select({ default: true, android: false }),
  showsBackgroundLocationIndicator: true,
  stopOnStationary: false,
  backgroundPermissionRationale: {
    title: "Permission required",
    message: "This app needs background location access to track your activity while the app is not open.",
  },
});

// Additional configuration when recording starts:
await BackgroundGeolocation.setConfig({
  url: `${beConfig.config?.tracking?.serverUrl}/batch`,
  authorization: {
    strategy: 'JWT',
    accessToken: idToken!,
  },
  extras: {
    roundId: roundQuery.data?.id!,
    versionNumber: `${VersionNumber.appVersion}(${VersionNumber.buildVersion})`,
  },
});

Expected Behavior

We expect the batch syncing process not to interfere with our API calls. Specifically, we would like to know when the background sync is in progress (via a state like isLoading or a similar mechanism) so we can avoid triggering the HTTPService is busy error. While the onHttp method allows us to receive responses after sync requests, we are looking for a way to be informed when requests are being sent in the first place.

Actual Behavior

During the call to our API, we intermittently receive the HTTPService is busy error. This is likely due to the background syncing process initiated by the background-geolocation library, which may be blocking the axios thread.

Here’s the code that triggers the error:

const onRoundCompletedAsync = async () => {
  try {
    await BackgroundGeolocation.sync();
    await endRoundQuery.mutateAsync();

    await BackgroundGeolocation.setConfig({
      isMoving: false,
    });
    await BackgroundGeolocation.stop();

    queryClient.invalidateQueries(['user_profile']);
    queryClient.invalidateQueries(['user_balance']);
  } catch (error) {
    // This is where we encounter the `HTTPService is busy` error
    console.error('Error during round completion:', error);
  }
}

Steps to Reproduce

1.  Configure BackgroundGeolocation with the settings above, ensuring batchSync: true.
2.  Accumulate sufficient GPS points (over 1500) so that batch sync is triggered during API calls.
3.  Call await BackgroundGeolocation.sync(); and observe that the error HTTPService is busy occurs.

Context

We are trying to determine when the background geolocation service is actively syncing data, so that we can manage API calls more intelligently and avoid conflicts. The ideal solution would involve subscribing to a sync state or knowing precisely when the batch sync process is sending requests.

Debug logs

Here’s an excerpt of the logs where the issue arises:

Logs Error: HTTPService is busy
christocracy commented 3 days ago

we are looking for a way to be informed when requests are being sent in the first place.

Why? What would you do differently? If the http service is busy, that means it’s currently uploading records, which is what you set out to do by calling .sync().

“HTTP Service is busy” is not an error. It’s more like “I’m already on it”. It’s completely harmless.

louwjlabuschagne commented 2 days ago

Just want to confirm.

If we call .sync and get “HTTP Service is busy”, we can ignore it and we continue with our logic, which involves calling await BackgroundGeolocation.stop(); as shown above - the batch syncing won't stop or have a problem?

So we can continue our business logic and the package will ensure all datapoints are sync even if .stop was called?

christocracy commented 2 days ago
  • the batch syncing won't stop or have a problem?

No. The HTTP service is independent. Once started, it won’t stop until all records in the database are emptied. There is only ever one instance of the http service operating at any given time, posting records synchronously.

So we can continue our business logic

Yes.

louwjlabuschagne commented 1 day ago

Follow-up Question: Is .sync Necessary?

I wanted to clarify whether we actually need to manually call .sync. My understanding is that the batch syncing mechanism will automatically sync data as needed, so there shouldn’t be a need to force a sync.

Here’s the setup when we start a “round”:

await BackgroundGeolocation.setConfig({
  url: `${beConfig.config?.tracking?.serverUrl}/batch`,
  authorization: {
    strategy: 'JWT',
    accessToken: idToken!,
  },
  extras: {
    roundId: roundQuery.data?.id!,
    versionNumber: `${VersionNumber.appVersion}(${VersionNumber.buildVersion})`,
  },
});

At the end of the round (typically after 4 hours), we stop recording GPS data.

What I’d like to confirm is whether we need to worry about extras.roundId lingering in the database. Specifically, will the records from the previous round persist with the old roundId? When a new round starts, will the new extras.roundId be applied to new records, without requiring a .sync to ensure the previous round’s records are fully synced? Can we rely on the batch sync mechanism to handle everything as we continue recording new data?