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.66k stars 426 forks source link

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

Open louwjlabuschagne opened 1 month ago

louwjlabuschagne commented 1 month 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 1 month 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 1 month 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 1 month 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 month 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?

Chugunenok commented 1 month ago

Once started, it won’t stop until all records in the database are emptied.

This causes bad behavior: if http request takes a long time, then BackgroundGeolocation.sync() will not stop until you do BackgroundGeolocation.stop(). While http client syncs a single point to server (http request can take more than 1 second), we get new point (iOS sends actual point every ~1 second). It will never stop sync if http request is slower than the period of getting updated location. When I call BackgroundGeolocation.sync(), I expect it to sync only points collected until current time. If I want to sync everything - I will do while(pointsExists) {await BackgroundGeolocation.sync(); pointsExists = ...} loop manually. We have a custom GPS data processing on server and we want to collect ALL points. And we want to sync points at least every 10 minutes. Now the only way to do this is to manually read points -> send to server -> delete points.

christocracy commented 1 month ago

Perhaps you're interested in Config.batchSync, Config.batchSyncThreshold and Config.maxBatchSize. Also see the API docs HTTP Guide

Chugunenok commented 1 month ago

Config.batchSyncThreshold

Do you mean autoSyncThreshold? I cannot use autoSyncThreshold because I need to send data every 10 minutes regardless of collected points amount. I am using batchSync=true and maxBatchSize=1000. Every 10 minutes I run BackgroundGeolocation.sync(), it sends several 1000 points batches and then starts sending 1-2 points batches continuously, it stops only when iOS is providing new points slower than library syncs it to server.

github-actions[bot] commented 1 day ago

This issue is stale because it has been open for 30 days with no activity.