@@ -34,103 +34,73 @@ func _calendarClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _Cal
34
34
}
35
35
36
36
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
37
- struct CalendarCache : Sendable {
37
+ struct CalendarCache : Sendable , ~ Copyable {
38
38
// MARK: - State
39
39
40
- struct State : Sendable {
41
- // If nil, the calendar has been invalidated and will be created next time State.current() is called
42
- private var currentCalendar : ( any _CalendarProtocol ) ?
43
- private var autoupdatingCurrentCalendar : _CalendarAutoupdating ?
44
- private var fixedCalendars : [ Calendar . Identifier : any _CalendarProtocol ] = [ : ]
45
-
46
- private var noteCount = - 1
47
- private var wasResetManually = false
48
-
49
- mutating func check( ) {
50
- #if FOUNDATION_FRAMEWORK
51
- // On Darwin we listen for certain distributed notifications to reset the current Calendar.
52
- let newNoteCount = _CFLocaleGetNoteCount ( ) + _CFTimeZoneGetNoteCount( ) + Int( _CFCalendarGetMidnightNoteCount ( ) )
53
- #else
54
- let newNoteCount = 1
55
- #endif
56
- if newNoteCount != noteCount || wasResetManually {
57
- // rdar://102017659
58
- // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
59
- // calendar. Creating the current calendar gets the current locale, decodes a plist
60
- // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
61
- // leads to a deadlock if we are also initializing a class on the current thread
62
- currentCalendar = nil
63
- fixedCalendars = [ : ]
64
-
65
- noteCount = newNoteCount
66
- wasResetManually = false
67
- }
68
- }
69
-
70
- mutating func current( ) -> any _CalendarProtocol {
71
- check ( )
72
- if let currentCalendar {
73
- return currentCalendar
74
- } else {
75
- let id = Locale . current. _calendarIdentifier
76
- // If we cannot create the right kind of class, we fail immediately here
77
- let calendarClass = _calendarClass ( identifier: id, useGregorian: true ) !
78
- let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
79
- currentCalendar = calendar
80
- return calendar
81
- }
40
+ static let cache = CalendarCache ( )
41
+
42
+ // 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.
43
+
44
+ private let _current = LockedState < ( any _CalendarProtocol ) ? > ( initialState: nil )
45
+ private let _fixed = LockedState < [ Calendar . Identifier : any _CalendarProtocol ] > ( initialState: [ : ] )
46
+
47
+ fileprivate init ( ) {
48
+ }
49
+
50
+ var current : any _CalendarProtocol {
51
+ if let result = _current. withLock ( { $0 } ) {
52
+ return result
82
53
}
54
+
55
+ let id = Locale . current. _calendarIdentifier
56
+ // If we cannot create the right kind of class, we fail immediately here
57
+ let calendarClass = _calendarClass ( identifier: id, useGregorian: true ) !
58
+ let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
83
59
84
- mutating func autoupdatingCurrent( ) -> any _CalendarProtocol {
85
- if let autoupdatingCurrentCalendar {
86
- return autoupdatingCurrentCalendar
60
+ return _current. withLock {
61
+ if let current = $0 {
62
+ // Someone beat us to setting it - use the existing one
63
+ return current
87
64
} else {
88
- let calendar = _CalendarAutoupdating ( )
89
- autoupdatingCurrentCalendar = calendar
65
+ $0 = calendar
90
66
return calendar
91
67
}
92
68
}
93
-
94
- mutating func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
95
- check ( )
96
- if let cached = fixedCalendars [ id] {
97
- return cached
98
- } else {
99
- // If we cannot create the right kind of class, we fail immediately here
100
- let calendarClass = _calendarClass ( identifier: id, useGregorian: true ) !
101
- let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
102
- fixedCalendars [ id] = new
103
- return new
104
- }
105
- }
106
-
107
- mutating func reset( ) {
108
- wasResetManually = true
109
- }
110
- }
111
-
112
- let lock : LockedState < State >
113
-
114
- static let cache = CalendarCache ( )
115
-
116
- fileprivate init ( ) {
117
- lock = LockedState ( initialState: State ( ) )
118
69
}
119
-
70
+
120
71
func reset( ) {
121
- lock. withLock { $0. reset ( ) }
122
- }
123
-
124
- var current : any _CalendarProtocol {
125
- lock. withLock { $0. current ( ) }
72
+ // rdar://102017659
73
+ // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
74
+ // calendar. Creating the current calendar gets the current locale, decodes a plist
75
+ // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
76
+ // leads to a deadlock if we are also initializing a class on the current thread
77
+ _current. withLock { $0 = nil }
78
+ _fixed. withLock { $0 = [ : ] }
126
79
}
127
80
128
- var autoupdatingCurrent : any _CalendarProtocol {
129
- lock. withLock { $0. autoupdatingCurrent ( ) }
130
- }
81
+ // MARK: Singletons
82
+
83
+ static let autoupdatingCurrent = _CalendarAutoupdating ( )
84
+
85
+ // MARK: -
131
86
132
87
func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
133
- lock. withLock { $0. fixed ( id) }
88
+ if let existing = _fixed. withLock ( { $0 [ id] } ) {
89
+ return existing
90
+ }
91
+
92
+ // If we cannot create the right kind of class, we fail immediately here
93
+ let calendarClass = _calendarClass ( identifier: id, useGregorian: true ) !
94
+ let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
95
+
96
+ return _fixed. withLock {
97
+ if let existing = $0 [ id] {
98
+ return existing
99
+ } else {
100
+ $0 [ id] = new
101
+ return new
102
+ }
103
+ }
134
104
}
135
105
136
106
func fixed( identifier: Calendar . Identifier , locale: Locale ? , timeZone: TimeZone ? , firstWeekday: Int ? , minimumDaysInFirstWeek: Int ? , gregorianStartDate: Date ? ) -> any _CalendarProtocol {
0 commit comments