Skip to content

CRITICAL: DateFormatter thread safety issue in Date+RFC2822 #134

@leogdion

Description

@leogdion

Severity: 🔴 Critical
File: Sources/BushelUtilities/Extensions/Date+RFC2822.swift:43
PR: #126

Issue

The shared static DateFormatter can cause race conditions when date(from:) is called concurrently from multiple threads. DateFormatter has internal mutable state that is not thread-safe, which is particularly problematic for HTTP header parsing where concurrent requests are common.

Current Code

extension Date {
  private static let rfc2822Formatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    return formatter
  }()

  public init?(rfc2822String: String) {
    guard let date = Self.rfc2822Formatter.date(from: rfc2822String) else {
      return nil
    }
    self = date
  }
}

Recommended Solutions

Option 1: Create a new formatter instance per call (simplest)

extension Date {
  private static func makeRFC2822Formatter() -> DateFormatter {
    let formatter = DateFormatter()
    formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    return formatter
  }

  public init?(rfc2822String: String) {
    guard let date = Self.makeRFC2822Formatter().date(from: rfc2822String) else {
      return nil
    }
    self = date
  }
}

Option 2: Use a lock for thread-safe access (better performance)

extension Date {
  private static let formatterLock = NSLock()

  private static let rfc2822Formatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    return formatter
  }()

  public init?(rfc2822String: String) {
    formatterLock.lock()
    defer { formatterLock.unlock() }
    guard let date = Self.rfc2822Formatter.date(from: rfc2822String) else {
      return nil
    }
    self = date
  }
}

Impact

This is a critical issue that can cause:

  • Race conditions in production
  • Unpredictable parsing failures
  • Data corruption
  • Crashes in concurrent scenarios

Source: CodeRabbit AI review of PR #126

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions