0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ninda 8日目: タイムアウト処理 〜ブロッキング操作の制御〜

Posted at

今日のゴール

  • ブロッキング操作のタイムアウト設定を理解する
  • タイムアウト例外の適切な処理方法を学ぶ
  • 実践的なタイムアウト戦略を身につける

ブロッキング操作の危険性

readtakeはマッチするタプルが見つかるまで永久にブロックします。

# ⚠️ 危険: マッチするタプルがなければ永久に待機
let result = await ts.readAsync(toPattern(strVal("nonexistent"), nilValue()))
# ここに到達しない可能性がある

本番環境では、必ずタイムアウトを設定すべきです。


タイムアウトの指定方法

readAsynctakeAsynctimeoutパラメータを受け取ります。

try:
  let result = await ts.readAsync(pattern, timeout = 5000)  # 5秒
  echo "Found: ", result
except TimeoutError:
  echo "Timeout: No matching tuple found within 5 seconds"

タイムアウト値の意味

動作
-1 タイムアウトなし(永久に待機)デフォルト
0 即座に返る(tryRead/tryTakeと同等)
> 0 指定ミリ秒待機してタイムアウト

例外処理のパターン

パターン1: try-except

try:
  let result = await ts.takeAsync(jobPattern, timeout = 3000)
  processJob(result)
except TimeoutError:
  echo "No job available, will retry later"
except CatchableError as e:
  echo "Unexpected error: ", e.msg

パターン2: 非ブロッキング操作

タイムアウトの代わりにtryRead/tryTakeを使う方法:

var attempts = 0
while attempts < maxAttempts:
  let result = await ts.tryReadAsync(pattern)
  if result.isSome:
    return result.get()
  attempts.inc
  await sleepAsync(pollInterval)
echo "Gave up after ", maxAttempts, " attempts"

パターン3: デフォルト値を返す

proc readWithDefault(pattern: Pattern, default: Tuple, timeout: int64): Future[Tuple] {.async.} =
  try:
    return await ts.readAsync(pattern, timeout)
  except TimeoutError:
    return default

実践的なタイムアウト戦略

戦略1: 指数バックオフ

最初は短いタイムアウトで試し、徐々に長くする。

var timeout = 100  # 初期値
const maxTimeout = 10000
const multiplier = 2

while timeout <= maxTimeout:
  try:
    return await ts.readAsync(pattern, timeout.int64)
  except TimeoutError:
    timeout = min(timeout * multiplier, maxTimeout)

利点: 軽い障害なら早く検出、重い障害でも諦めずに待機

戦略2: キャンセル可能な待機

var cancelled = false

proc waitForJob() {.async.} =
  while not cancelled:
    let result = await ts.tryTakeAsync(jobPattern)
    if result.isSome:
      return result.get()
    await sleepAsync(100)

proc cancel() =
  cancelled = true

ベストプラクティス

1. 適切なデフォルト値を設定

const
  DEFAULT_READ_TIMEOUT = 5000'i64    # 5秒
  DEFAULT_TAKE_TIMEOUT = 10000'i64   # 10秒
  LONG_OPERATION_TIMEOUT = 60000'i64 # 1分

2. 操作種別でタイムアウトを変える

操作 タイムアウト 理由
設定読み込み 短い(1秒) 設定はすぐ見つかるべき
ジョブ待機 長い(30秒) ジョブは非同期に来る
外部連携 非常に長い(120秒) 外部システムは遅い可能性

3. エラーメッセージを明確に

except TimeoutError:
  raise newException(TimeoutError,
    "Failed to get job of type '" & jobType & "' within 5 seconds")

まとめ

学んだこと

トピック ポイント
timeout パラメータ ミリ秒単位で指定
TimeoutError タイムアウト時に発生
戦略 try-except、非ブロッキング、指数バックオフ
ベストプラクティス デフォルト値、操作別設定、明確なエラー

選び方の指針

状況 推奨
結果が必要 try-except + タイムアウト
ポーリング tryRead/tryTake
断続的な障害 指数バックオフ
ユーザー操作 キャンセル可能な待機

演習問題

問題8-1: リトライ機構

3回までリトライし、各リトライで待ち時間を2倍にする関数を実装してください。

ヒント
  • 初期タイムアウト: 500ms
  • リトライごとに: 500ms → 1000ms → 2000ms
  • 最後まで失敗したらnone(Tuple)を返す
proc readWithRetry(pattern: Pattern): Future[Option[Tuple]] {.async.} =
  var timeout = 500
  for attempt in 1..3:
    try:
      return some(await ts.readAsync(pattern, timeout.int64))
    except TimeoutError:
      timeout *= 2
  return none(Tuple)

問題8-2: タイムアウト付きパイプライン

入力→処理→出力のパイプラインで、各ステージに適切なタイムアウトを設定してください。

ヒント
  • 入力ステージ: データソースからの読み取り(5秒)
  • 処理ステージ: 変換処理(10秒)
  • 出力ステージ: 結果の書き込み(3秒)
  • いずれかがタイムアウトしたら、エラーをログに記録して次のデータへ

問題8-3: サーキットブレーカー

連続して3回タイムアウトしたら、30秒間リクエストを拒否する「サーキットブレーカー」を実装してください。

ヒント
  • 状態: Closed(正常)、Open(拒否中)、HalfOpen(試行中)
  • 連続失敗カウンターを管理
  • Open状態では即座にエラーを返す
  • 30秒後にHalfOpenに移行して1回試行

前回: 名前付きスペース | 目次 | 次回: イベント通知

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?