32
32
#import " SPObjectAdditions.h"
33
33
#import < SPMySQL/SPMySQLStreamingResultStore.h>
34
34
#include < stdlib.h>
35
+ #include < mach/mach_time.h>
35
36
36
37
@interface SPDataStorage (Private_API)
37
38
38
39
- (void ) _checkNewRow : (NSMutableArray *)aRow ;
40
+ - (void )_recordClearingUnloadedColumnsAt : (uint64_t )now from : (NSArray *)callStack ;
41
+ - (void )_assesUnloadedColumnsIsSet ;
39
42
40
43
@end
41
44
45
+ static uint64_t _elapsedMilliSecondsSinceAbsoluteTime (uint64_t comparisonTime);
46
+
42
47
@implementation SPDataStorage
43
48
44
49
static inline NSMutableArray * SPDataStorageGetEditedRow (NSPointerArray * rowStore, NSUInteger rowIndex)
@@ -60,7 +65,13 @@ - (void) setDataStorage:(SPMySQLStreamingResultStore *)newDataStorage updatingEx
60
65
NSUInteger i;
61
66
editedRowCount = 0 ;
62
67
SPClear (editedRows);
63
- if (unloadedColumns) free (unloadedColumns), unloadedColumns = NULL ;
68
+ @synchronized (self) {
69
+ if (unloadedColumns) {
70
+ [self _recordClearingUnloadedColumnsAt: mach_absolute_time () from: [NSThread callStackSymbols ]];
71
+ free (unloadedColumns), unloadedColumns = NULL ;
72
+ }
73
+ }
74
+
64
75
65
76
if (dataStorage) {
66
77
@@ -81,9 +92,11 @@ - (void) setDataStorage:(SPMySQLStreamingResultStore *)newDataStorage updatingEx
81
92
[self resultStoreDidFinishLoadingData: dataStorage];
82
93
}
83
94
84
- unloadedColumns = calloc (numberOfColumns, sizeof (BOOL ));
85
- for (i = 0 ; i < numberOfColumns; i++) {
86
- unloadedColumns[i] = NO ;
95
+ @synchronized (self) {
96
+ unloadedColumns = calloc (numberOfColumns, sizeof (BOOL ));
97
+ for (i = 0 ; i < numberOfColumns; i++) {
98
+ unloadedColumns[i] = NO ;
99
+ }
87
100
}
88
101
}
89
102
@@ -110,10 +123,12 @@ - (NSMutableArray *) rowContentsAtIndex:(NSUInteger)anIndex
110
123
NSMutableArray *dataArray = SPMySQLResultStoreGetRow (dataStorage, anIndex);
111
124
112
125
// Modify unloaded cells as appropriate
113
- for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
114
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
115
- if (unloadedColumns[i]) {
116
- CFArraySetValueAtIndex ((CFMutableArrayRef )dataArray, i, [SPNotLoaded notLoaded ]);
126
+ @synchronized (self) {
127
+ [self _assesUnloadedColumnsIsSet ];
128
+ for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
129
+ if (unloadedColumns[i]) {
130
+ CFArraySetValueAtIndex ((CFMutableArrayRef )dataArray, i, [SPNotLoaded notLoaded ]);
131
+ }
117
132
}
118
133
}
119
134
@@ -140,9 +155,11 @@ - (id) cellDataAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex
140
155
}
141
156
142
157
// If the specified column is not loaded, return a SPNotLoaded reference
143
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
144
- if (unloadedColumns[columnIndex]) {
145
- return [SPNotLoaded notLoaded ];
158
+ @synchronized (self) {
159
+ [self _assesUnloadedColumnsIsSet ];
160
+ if (unloadedColumns[columnIndex]) {
161
+ return [SPNotLoaded notLoaded ];
162
+ }
146
163
}
147
164
148
165
// Return the content
@@ -175,9 +192,11 @@ - (id) cellPreviewAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex prev
175
192
}
176
193
177
194
// If the specified column is not loaded, return a SPNotLoaded reference
178
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
179
- if (unloadedColumns[columnIndex]) {
180
- return [SPNotLoaded notLoaded ];
195
+ @synchronized (self) {
196
+ [self _assesUnloadedColumnsIsSet ];
197
+ if (unloadedColumns[columnIndex]) {
198
+ return [SPNotLoaded notLoaded ];
199
+ }
181
200
}
182
201
183
202
// Return the content
@@ -203,9 +222,11 @@ - (BOOL) cellIsNullOrUnloadedAtRow:(NSUInteger)rowIndex column:(NSUInteger)colum
203
222
[NSException raise :NSRangeException format: @" Requested storage column (col %llu ) beyond bounds (%llu )" , (unsigned long long )columnIndex, (unsigned long long )numberOfColumns];
204
223
}
205
224
206
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
207
- if (unloadedColumns[columnIndex]) {
208
- return YES ;
225
+ @synchronized (self) {
226
+ [self _assesUnloadedColumnsIsSet ];
227
+ if (unloadedColumns[columnIndex]) {
228
+ return YES ;
229
+ }
209
230
}
210
231
211
232
return [dataStorage cellIsNullAtRow: rowIndex column: columnIndex];
@@ -236,10 +257,12 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object
236
257
targetRow = SPMySQLResultStoreGetRow (dataStorage, state->state );
237
258
238
259
// Modify unloaded cells as appropriate
239
- for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
240
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
241
- if (unloadedColumns[i]) {
242
- CFArraySetValueAtIndex ((CFMutableArrayRef )targetRow, i, [SPNotLoaded notLoaded ]);
260
+ @synchronized (self) {
261
+ [self _assesUnloadedColumnsIsSet ];
262
+ for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
263
+ if (unloadedColumns[i]) {
264
+ CFArraySetValueAtIndex ((CFMutableArrayRef )targetRow, i, [SPNotLoaded notLoaded ]);
265
+ }
243
266
}
244
267
}
245
268
}
@@ -397,8 +420,10 @@ - (void) setColumnAsUnloaded:(NSUInteger)columnIndex
397
420
if (columnIndex >= numberOfColumns) {
398
421
[NSException raise :NSRangeException format: @" Invalid column set as unloaded; requested column index (%llu ) beyond bounds (%llu )" , (unsigned long long )columnIndex, (unsigned long long )numberOfColumns];
399
422
}
400
- NSAssert (unloadedColumns != NULL , @" unloadedColumns not loaded!" );
401
- unloadedColumns[columnIndex] = YES ;
423
+ @synchronized (self) {
424
+ [self _assesUnloadedColumnsIsSet ];
425
+ unloadedColumns[columnIndex] = YES ;
426
+ }
402
427
}
403
428
404
429
#pragma mark - Basic information
@@ -451,15 +476,29 @@ - (id) init {
451
476
452
477
numberOfColumns = 0 ;
453
478
editedRowCount = 0 ;
479
+
480
+ _debugInfo = nil ;
481
+ _debugTime = mach_absolute_time ();
454
482
}
455
483
return self;
456
484
}
457
485
458
486
- (void ) dealloc {
459
487
SPClear (dataStorage);
460
488
SPClear (editedRows);
461
- if (unloadedColumns) free (unloadedColumns), unloadedColumns = NULL ;
462
-
489
+ @synchronized (self) {
490
+ if (unloadedColumns) {
491
+ [self _recordClearingUnloadedColumnsAt: mach_absolute_time () from: [NSThread callStackSymbols ]];
492
+ free (unloadedColumns), unloadedColumns = NULL ;
493
+ }
494
+ }
495
+ // this is very very unlikely, but if another thread had been waiting on the lock
496
+ // right before we free'd unloadedColumns, it should get it before we can release
497
+ // _debugInfo, too.
498
+ @synchronized (self) {
499
+ SPClear (_debugInfo);
500
+ }
501
+
463
502
[super dealloc ];
464
503
}
465
504
@@ -474,5 +513,37 @@ - (void) _checkNewRow:(NSMutableArray *)aRow
474
513
}
475
514
}
476
515
516
+ // DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!!
517
+ - (void )_recordClearingUnloadedColumnsAt : (uint64_t )now from : (NSArray *)callStack
518
+ {
519
+ _debugTime = now;
520
+ SPClear (_debugInfo);
521
+ _debugInfo = [[NSString alloc ] initWithFormat: @" Thread: %@ , Stack: %@ " ,[NSThread currentThread ],callStack];
522
+ }
523
+
524
+ // DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!!
525
+ - (void )_assesUnloadedColumnsIsSet
526
+ {
527
+ if (unloadedColumns != NULL )
528
+ return ;
529
+
530
+ uint64_t timeDiff = _elapsedMilliSecondsSinceAbsoluteTime (_debugTime);
531
+
532
+ NSString *msg;
533
+ if (!_debugInfo)
534
+ msg = [NSString stringWithFormat: @" unloadedColumns is not set and never has been since the object was created %llu ms ago." ,timeDiff];
535
+ else
536
+ msg = [NSString stringWithFormat: @" unloadedColumns was last cleared %llu ms ago at %@ " ,timeDiff,_debugInfo];
537
+
538
+ @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: msg userInfo: nil ];
539
+ }
477
540
478
541
@end
542
+
543
+ static uint64_t _elapsedMilliSecondsSinceAbsoluteTime (uint64_t comparisonTime)
544
+ {
545
+ uint64_t elapsedTime_t = mach_absolute_time () - comparisonTime;
546
+ Nanoseconds elapsedTime = AbsoluteToNanoseconds (*(AbsoluteTime *)&(elapsedTime_t));
547
+
548
+ return (UnsignedWideToUInt64 (elapsedTime) / 1000000ULL );
549
+ }
0 commit comments