@@ -16,7 +16,7 @@ import CoreFoundation
1616#endif
1717
1818/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
19- struct CalendarCache : Sendable , ~ Copyable {
19+ struct CalendarCache : Sendable {
2020
2121 // MARK: - Concrete Classes
2222
@@ -38,71 +38,103 @@ struct CalendarCache : Sendable, ~Copyable {
3838 }
3939#endif
4040 }
41+
42+ // MARK: - State
4143
42- static let cache = CalendarCache ( )
43-
44- // The values stored in these two locks do not depend upon each other, so it is safe to access them with separate locks. This helps avoids contention on a single lock.
45-
46- private let _current = LockedState < ( any _CalendarProtocol ) ? > ( initialState: nil )
47- private let _fixed = LockedState < [ Calendar . Identifier : any _CalendarProtocol ] > ( initialState: [ : ] )
48-
49- fileprivate init ( ) {
50- }
51-
52- var current : any _CalendarProtocol {
53- if let result = _current. withLock ( { $0 } ) {
54- return result
44+ struct State : Sendable {
45+ // If nil, the calendar has been invalidated and will be created next time State.current() is called
46+ private var currentCalendar : ( any _CalendarProtocol ) ?
47+ private var autoupdatingCurrentCalendar : _CalendarAutoupdating ?
48+ private var fixedCalendars : [ Calendar . Identifier : any _CalendarProtocol ] = [ : ]
49+
50+ private var noteCount = - 1
51+ private var wasResetManually = false
52+
53+ mutating func check( ) {
54+ #if FOUNDATION_FRAMEWORK
55+ // On Darwin we listen for certain distributed notifications to reset the current Calendar.
56+ let newNoteCount = _CFLocaleGetNoteCount ( ) + _CFTimeZoneGetNoteCount( ) + Int( _CFCalendarGetMidnightNoteCount ( ) )
57+ #else
58+ let newNoteCount = 1
59+ #endif
60+ if newNoteCount != noteCount || wasResetManually {
61+ // rdar://102017659
62+ // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
63+ // calendar. Creating the current calendar gets the current locale, decodes a plist
64+ // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
65+ // leads to a deadlock if we are also initializing a class on the current thread
66+ currentCalendar = nil
67+ fixedCalendars = [ : ]
68+
69+ noteCount = newNoteCount
70+ wasResetManually = false
71+ }
72+ }
73+
74+ mutating func current( ) -> any _CalendarProtocol {
75+ check ( )
76+ if let currentCalendar {
77+ return currentCalendar
78+ } else {
79+ let id = Locale . current. _calendarIdentifier
80+ // If we cannot create the right kind of class, we fail immediately here
81+ let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
82+ let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
83+ currentCalendar = calendar
84+ return calendar
85+ }
5586 }
56-
57- let id = Locale . current. _calendarIdentifier
58- // If we cannot create the right kind of class, we fail immediately here
59- let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
60- let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
6187
62- return _current. withLock {
63- if let current = $0 {
64- // Someone beat us to setting it - use the existing one
65- return current
88+ mutating func autoupdatingCurrent( ) -> any _CalendarProtocol {
89+ if let autoupdatingCurrentCalendar {
90+ return autoupdatingCurrentCalendar
6691 } else {
67- $0 = calendar
92+ let calendar = _CalendarAutoupdating ( )
93+ autoupdatingCurrentCalendar = calendar
6894 return calendar
6995 }
7096 }
97+
98+ mutating func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
99+ check ( )
100+ if let cached = fixedCalendars [ id] {
101+ return cached
102+ } else {
103+ // If we cannot create the right kind of class, we fail immediately here
104+ let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
105+ let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
106+ fixedCalendars [ id] = new
107+ return new
108+ }
109+ }
110+
111+ mutating func reset( ) {
112+ wasResetManually = true
113+ }
71114 }
72-
115+
116+ let lock : LockedState < State >
117+
118+ static let cache = CalendarCache ( )
119+
120+ fileprivate init ( ) {
121+ lock = LockedState ( initialState: State ( ) )
122+ }
123+
73124 func reset( ) {
74- // rdar://102017659
75- // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
76- // calendar. Creating the current calendar gets the current locale, decodes a plist
77- // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
78- // leads to a deadlock if we are also initializing a class on the current thread
79- _current. withLock { $0 = nil }
80- _fixed. withLock { $0 = [ : ] }
125+ lock. withLock { $0. reset ( ) }
126+ }
127+
128+ var current : any _CalendarProtocol {
129+ lock. withLock { $0. current ( ) }
81130 }
82131
83- // MARK: Singletons
84-
85- static let autoupdatingCurrent = _CalendarAutoupdating ( )
86-
87- // MARK: -
132+ var autoupdatingCurrent : any _CalendarProtocol {
133+ lock. withLock { $0. autoupdatingCurrent ( ) }
134+ }
88135
89136 func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
90- if let existing = _fixed. withLock ( { $0 [ id] } ) {
91- return existing
92- }
93-
94- // If we cannot create the right kind of class, we fail immediately here
95- let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
96- let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
97-
98- return _fixed. withLock {
99- if let existing = $0 [ id] {
100- return existing
101- } else {
102- $0 [ id] = new
103- return new
104- }
105- }
137+ lock. withLock { $0. fixed ( id) }
106138 }
107139
108140 func fixed( identifier: Calendar . Identifier , locale: Locale ? , timeZone: TimeZone ? , firstWeekday: Int ? , minimumDaysInFirstWeek: Int ? , gregorianStartDate: Date ? ) -> any _CalendarProtocol {
0 commit comments