@@ -16,7 +16,7 @@ import CoreFoundation
16
16
#endif
17
17
18
18
/// 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 {
19
+ struct CalendarCache : Sendable , ~ Copyable {
20
20
21
21
// MARK: - Concrete Classes
22
22
@@ -38,103 +38,71 @@ struct CalendarCache : Sendable {
38
38
}
39
39
#endif
40
40
}
41
-
42
- // MARK: - State
43
41
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
- }
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
86
55
}
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 )
87
61
88
- mutating func autoupdatingCurrent( ) -> any _CalendarProtocol {
89
- if let autoupdatingCurrentCalendar {
90
- return autoupdatingCurrentCalendar
62
+ return _current. withLock {
63
+ if let current = $0 {
64
+ // Someone beat us to setting it - use the existing one
65
+ return current
91
66
} else {
92
- let calendar = _CalendarAutoupdating ( )
93
- autoupdatingCurrentCalendar = calendar
67
+ $0 = calendar
94
68
return calendar
95
69
}
96
70
}
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
- }
114
71
}
115
-
116
- let lock : LockedState < State >
117
-
118
- static let cache = CalendarCache ( )
119
-
120
- fileprivate init ( ) {
121
- lock = LockedState ( initialState: State ( ) )
122
- }
123
-
72
+
124
73
func reset( ) {
125
- lock. withLock { $0. reset ( ) }
126
- }
127
-
128
- var current : any _CalendarProtocol {
129
- lock. withLock { $0. current ( ) }
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 = [ : ] }
130
81
}
131
82
132
- var autoupdatingCurrent : any _CalendarProtocol {
133
- lock. withLock { $0. autoupdatingCurrent ( ) }
134
- }
83
+ // MARK: Singletons
84
+
85
+ static let autoupdatingCurrent = _CalendarAutoupdating ( )
86
+
87
+ // MARK: -
135
88
136
89
func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
137
- lock. withLock { $0. fixed ( id) }
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
+ }
138
106
}
139
107
140
108
func fixed( identifier: Calendar . Identifier , locale: Locale ? , timeZone: TimeZone ? , firstWeekday: Int ? , minimumDaysInFirstWeek: Int ? , gregorianStartDate: Date ? ) -> any _CalendarProtocol {
0 commit comments