Skip to content

startLocationUpdatesAsyncに指数バックオフ付きリトライ処理を追加#5401

Merged
TinyKitten merged 5 commits intodevfrom
claude/retry-location-updates-zAte0
Feb 21, 2026
Merged

startLocationUpdatesAsyncに指数バックオフ付きリトライ処理を追加#5401
TinyKitten merged 5 commits intodevfrom
claude/retry-location-updates-zAte0

Conversation

@TinyKitten
Copy link
Member

@TinyKitten TinyKitten commented Feb 21, 2026

https://claude.ai/code/session_011UAtpQJK1XnENVkcaQRmMi

Summary by CodeRabbit

  • バグ修正

    • バックグラウンド位置情報更新の開始処理で失敗時に最大3回まで自動リトライを行うようにし、再試行間に基準遅延を用いたバックオフを適用します。アンマウント/クリーンアップ時は再試行を中止し、起動処理中の不要な更新を停止、失敗時には警告ログを出力します。
  • テスト

    • リトライ成功・上限到達・クリーンアップ中の停止・停止失敗など、さまざまな再試行シナリオを検証するテストを追加・拡充しました。

@github-actions github-actions bot added the react label Feb 21, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

startLocationUpdatesAsync 呼び出しに最大3回の再試行(基底遅延1000msでのバックオフ)を追加し、アンマウント時のキャンセルと再試行/停止失敗の警告ログを導入。関連定数を追加し、再試行を検証するテストを拡張。

変更内容

Cohort / File(s) Summary
定数追加
src/constants/location.ts
LOCATION_START_MAX_RETRIES = 3LOCATION_START_RETRY_BASE_DELAY_MS = 1000 を追加でエクスポート。
フック実装
src/hooks/useStartBackgroundLocationUpdates.ts
startLocationUpdatesAsync に最大試行回数と指数的バックオフを用いる再試行ループ、待機ヘルパー、キャンセルフラグ(アンマウントで設定)、再試行ごとの警告と最終失敗ログ、アンマウント中の stop 呼び出しと stop の失敗警告を追加。watchPositionAsync の既存効果は変更なし。
テスト拡張
src/hooks/useStartBackgroundLocationUpdates.test.tsx
再試行成功・上限到達・アンマウント中のクリーンアップ・stop失敗・アンマウントでのリトライ停止等のシナリオを追加。fake timers と console.warn スパイを導入してタイミングとログを検証。

Sequence Diagram(s)

sequenceDiagram
    participant Hook as Hook (useStartBackgroundLocationUpdates)
    participant LocationAPI as Expo Location API
    participant Timer as Backoff Timer
    participant Cleanup as Component Unmount

    Hook->>LocationAPI: startLocationUpdatesAsync()
    alt success
        LocationAPI-->>Hook: resolved
    else failure
        LocationAPI-->>Hook: rejected (error)
        Hook->>Hook: console.warn("retry n / max")
        Hook->>Timer: wait(backoffDelay)
        Timer-->>Hook: timer done
        alt cancelled (Unmount happened)
            Cleanup->>Hook: set cancelled=true
            Hook->>LocationAPI: stopLocationUpdatesAsync() (if started)
            LocationAPI-->>Hook: resolved/rejected
        else retry
            Hook->>LocationAPI: startLocationUpdatesAsync() (retry n+1)
        end
    end
Loading

推定コードレビュー工数

🎯 4 (Complex) | ⏱️ ~45分

関連する可能性のあるプルリクエスト

🐰✨ 三度跳ねるウサギだよ、
まずは一回、次にまた一回、
千の刻(ms)で深呼吸して、
アンマウントでそっと止める、
成功まで諦めずに跳ねるよ。

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRのタイトルは「startLocationUpdatesAsyncに指数バックオフ付きリトライ処理を追加」であり、変更内容と一致しています。実装の主要な変更点である、リトライロジックの追加と指数バックオフの導入を正確に反映している明確なタイトルです。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/retry-location-updates-zAte0

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useStartBackgroundLocationUpdates.test.tsx (1)

39-45: ⚠️ Potential issue | 🟡 Minor

jest.clearAllMocks は afterEach で実行してください。

🧹 修正案
   beforeEach(() => {
-    jest.clearAllMocks();
     mockAutoModeEnabled = false;
     mockStartLocationUpdatesAsync.mockResolvedValue(undefined);
     mockStopLocationUpdatesAsync.mockResolvedValue(undefined);
     mockWatchPositionAsync.mockResolvedValue({ remove: mockRemove });
   });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
As per coding guidelines, "Call `jest.clearAllMocks()` in `afterEach` blocks in all test files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx` around lines 39 - 45,
Move the call to jest.clearAllMocks() out of the beforeEach and into an
afterEach block; keep the mock setup (mockAutoModeEnabled = false,
mockStartLocationUpdatesAsync.mockResolvedValue(undefined),
mockStopLocationUpdatesAsync.mockResolvedValue(undefined),
mockWatchPositionAsync.mockResolvedValue({ remove: mockRemove })) in beforeEach,
and add an afterEach that calls jest.clearAllMocks() to conform to the
guideline; adjust the test file so only teardown clears mocks while
initialization remains in the beforeEach that references mockAutoModeEnabled,
mockStartLocationUpdatesAsync, mockStopLocationUpdatesAsync,
mockWatchPositionAsync and mockRemove.
🧹 Nitpick comments (1)
src/hooks/useStartBackgroundLocationUpdates.test.tsx (1)

3-7: リトライ待機時間は定数参照にすると保守しやすいです。

♻️ 修正案
 import {
   LOCATION_START_MAX_RETRIES,
+  LOCATION_START_RETRY_BASE_DELAY_MS,
   LOCATION_TASK_NAME,
   LOCATION_TASK_OPTIONS,
 } from '../constants';
@@
-      await jest.advanceTimersByTimeAsync(1000);
+      await jest.advanceTimersByTimeAsync(
+        LOCATION_START_RETRY_BASE_DELAY_MS
+      );
@@
-        await jest.advanceTimersByTimeAsync(1000 * 2 ** i);
+        await jest.advanceTimersByTimeAsync(
+          LOCATION_START_RETRY_BASE_DELAY_MS * 2 ** i
+        );

Also applies to: 126-151

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx` around lines 3 - 7,
Tests in useStartBackgroundLocationUpdates.test.tsx use hardcoded retry-wait
numbers; replace those literals with the retry-wait constant exported from
'../constants' (alongside the already-imported LOCATION_START_MAX_RETRIES,
LOCATION_TASK_NAME, LOCATION_TASK_OPTIONS) so the tests reference the canonical
value and stay in sync — update assertions, mocked timers/delays and any
wait-loop logic in the useStartBackgroundLocationUpdates test cases (including
the other block noted) to use that constant and add the import if it’s not
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/useStartBackgroundLocationUpdates.ts`:
- Around line 31-73: The async start loop in useStartBackgroundLocationUpdates
may start location updates after the hook was cancelled; after a successful call
to Location.startLocationUpdatesAsync(LOCATION_TASK_NAME) re-check the cancelled
flag and, if true, immediately call and await
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME) to ensure no leftover
updates run; update the code around the startLocationUpdatesAsync call (and its
surrounding try block) to perform this cancelled check and stop, referencing the
existing cancelled variable, LOCATION_TASK_NAME, startLocationUpdatesAsync and
stopLocationUpdatesAsync to locate where to add the guard.

---

Outside diff comments:
In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx`:
- Around line 39-45: Move the call to jest.clearAllMocks() out of the beforeEach
and into an afterEach block; keep the mock setup (mockAutoModeEnabled = false,
mockStartLocationUpdatesAsync.mockResolvedValue(undefined),
mockStopLocationUpdatesAsync.mockResolvedValue(undefined),
mockWatchPositionAsync.mockResolvedValue({ remove: mockRemove })) in beforeEach,
and add an afterEach that calls jest.clearAllMocks() to conform to the
guideline; adjust the test file so only teardown clears mocks while
initialization remains in the beforeEach that references mockAutoModeEnabled,
mockStartLocationUpdatesAsync, mockStopLocationUpdatesAsync,
mockWatchPositionAsync and mockRemove.

---

Nitpick comments:
In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx`:
- Around line 3-7: Tests in useStartBackgroundLocationUpdates.test.tsx use
hardcoded retry-wait numbers; replace those literals with the retry-wait
constant exported from '../constants' (alongside the already-imported
LOCATION_START_MAX_RETRIES, LOCATION_TASK_NAME, LOCATION_TASK_OPTIONS) so the
tests reference the canonical value and stay in sync — update assertions, mocked
timers/delays and any wait-loop logic in the useStartBackgroundLocationUpdates
test cases (including the other block noted) to use that constant and add the
import if it’s not present.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useStartBackgroundLocationUpdates.ts (1)

29-83: ⚠️ Potential issue | 🟡 Minor

キャンセル後の stop 失敗が start 失敗扱いになる点を分離してください
cancelled 分岐内の stop が失敗すると catch に流れて「開始失敗」ログ&待機が走るため、停止失敗は別ログで握りつぶして即 return する方が意図が明確です。

🛠️ 修正案
          if (cancelled) {
-            await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
+            await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME).catch(
+              (stopError) => {
+                console.warn(
+                  'バックグラウンド位置情報の更新停止に失敗しました:',
+                  stopError
+                );
+              }
+            );
             return;
          }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useStartBackgroundLocationUpdates.ts` around lines 29 - 83, The
start routine currently calls await
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME) inside the try block when
cancelled is true, and any error there bubbles to the surrounding catch which
treats it as a start failure (triggering retry logic); change this by wrapping
the cancelled-path stop call in its own try/catch so stop errors are logged
separately (e.g. "停止失敗") and swallowed, then immediately return without letting
the error propagate to the outer catch; update the async IIFE around
startLocationUpdatesAsync/stopLocationUpdatesAsync (referencing cancelled,
startLocationUpdatesAsync, stopLocationUpdatesAsync, LOCATION_TASK_NAME, and the
existing catch/attempt/LOCATION_START_MAX_RETRIES logic) so retries only run for
start failures and not for stop failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/hooks/useStartBackgroundLocationUpdates.ts`:
- Around line 29-83: The start routine currently calls await
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME) inside the try block when
cancelled is true, and any error there bubbles to the surrounding catch which
treats it as a start failure (triggering retry logic); change this by wrapping
the cancelled-path stop call in its own try/catch so stop errors are logged
separately (e.g. "停止失敗") and swallowed, then immediately return without letting
the error propagate to the outer catch; update the async IIFE around
startLocationUpdatesAsync/stopLocationUpdatesAsync (referencing cancelled,
startLocationUpdatesAsync, stopLocationUpdatesAsync, LOCATION_TASK_NAME, and the
existing catch/attempt/LOCATION_START_MAX_RETRIES logic) so retries only run for
start failures and not for stop failures.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useStartBackgroundLocationUpdates.test.tsx (1)

39-45: ⚠️ Potential issue | 🟡 Minor

afterEach で jest.clearAllMocks を呼ぶようにしてください。
beforeEach のみだとガイドライン違反です。

修正案
  beforeEach(() => {
-   jest.clearAllMocks();
    mockAutoModeEnabled = false;
    mockStartLocationUpdatesAsync.mockResolvedValue(undefined);
    mockStopLocationUpdatesAsync.mockResolvedValue(undefined);
    mockWatchPositionAsync.mockResolvedValue({ remove: mockRemove });
  });
+
+ afterEach(() => {
+   jest.clearAllMocks();
+ });
As per coding guidelines: Call `jest.clearAllMocks()` in `afterEach` blocks in all test files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx` around lines 39 - 45,
Add a teardown that calls jest.clearAllMocks() in an afterEach block for the
test file: ensure afterEach(() => { jest.clearAllMocks(); }) is present
alongside the existing beforeEach setup that initializes mockAutoModeEnabled,
mockStartLocationUpdatesAsync, mockStopLocationUpdatesAsync, and
mockWatchPositionAsync; keep the existing mockResolvedValue calls for
mockStartLocationUpdatesAsync, mockStopLocationUpdatesAsync, and
mockWatchPositionAsync but move/duplicate the jest.clearAllMocks call into
afterEach so mocks are cleared after each test run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/hooks/useStartBackgroundLocationUpdates.test.tsx`:
- Around line 39-45: Add a teardown that calls jest.clearAllMocks() in an
afterEach block for the test file: ensure afterEach(() => {
jest.clearAllMocks(); }) is present alongside the existing beforeEach setup that
initializes mockAutoModeEnabled, mockStartLocationUpdatesAsync,
mockStopLocationUpdatesAsync, and mockWatchPositionAsync; keep the existing
mockResolvedValue calls for mockStartLocationUpdatesAsync,
mockStopLocationUpdatesAsync, and mockWatchPositionAsync but move/duplicate the
jest.clearAllMocks call into afterEach so mocks are cleared after each test run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants