]>
Commit | Line | Data |
---|---|---|
f54d4287 BM |
1 | /* |
2 | * Copyright (c) 2000 by Hewlett-Packard Company. All rights reserved. | |
3 | * | |
4 | * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED | |
5 | * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. | |
6 | * | |
7 | * Permission is hereby granted to use or copy this program | |
8 | * for any purpose, provided the above notices are retained on all copies. | |
9 | * Permission to modify the code and to distribute modified code is granted, | |
10 | * provided the above notices are retained, and a notice that the code was | |
11 | * modified is included with the above copyright notice. | |
12 | */ | |
13 | ||
f54d4287 | 14 | #include "private/gc_priv.h" /* For GC_compare_and_exchange, GC_memory_barrier */ |
f54d4287 | 15 | |
4109fe85 BM |
16 | #if defined(GC_LINUX_THREADS) |
17 | ||
28f2ebcf BM |
18 | #include "private/specific.h" |
19 | ||
4d6ac542 HB |
20 | static tse invalid_tse = {INVALID_QTID, 0, 0, INVALID_THREADID}; |
21 | /* A thread-specific data entry which will never */ | |
22 | /* appear valid to a reader. Used to fill in empty */ | |
23 | /* cache entries to avoid a check for 0. */ | |
f54d4287 BM |
24 | |
25 | int PREFIXED(key_create) (tsd ** key_ptr, void (* destructor)(void *)) { | |
26 | int i; | |
27 | tsd * result = (tsd *)MALLOC_CLEAR(sizeof (tsd)); | |
28 | ||
4d6ac542 HB |
29 | /* A quick alignment check, since we need atomic stores */ |
30 | GC_ASSERT((unsigned long)(&invalid_tse.next) % sizeof(tse *) == 0); | |
f54d4287 BM |
31 | if (0 == result) return ENOMEM; |
32 | pthread_mutex_init(&(result -> lock), NULL); | |
33 | for (i = 0; i < TS_CACHE_SIZE; ++i) { | |
34 | result -> cache[i] = &invalid_tse; | |
35 | } | |
4d6ac542 HB |
36 | # ifdef GC_ASSERTIONS |
37 | for (i = 0; i < TS_HASH_SIZE; ++i) { | |
38 | GC_ASSERT(result -> hash[i] == 0); | |
39 | } | |
40 | # endif | |
f54d4287 BM |
41 | *key_ptr = result; |
42 | return 0; | |
43 | } | |
44 | ||
45 | int PREFIXED(setspecific) (tsd * key, void * value) { | |
46 | pthread_t self = pthread_self(); | |
47 | int hash_val = HASH(self); | |
48 | volatile tse * entry = (volatile tse *)MALLOC_CLEAR(sizeof (tse)); | |
49 | ||
4d6ac542 | 50 | GC_ASSERT(self != INVALID_THREADID); |
f54d4287 BM |
51 | if (0 == entry) return ENOMEM; |
52 | pthread_mutex_lock(&(key -> lock)); | |
53 | /* Could easily check for an existing entry here. */ | |
54 | entry -> next = key -> hash[hash_val]; | |
55 | entry -> thread = self; | |
56 | entry -> value = value; | |
4d6ac542 | 57 | GC_ASSERT(entry -> qtid == INVALID_QTID); |
f54d4287 BM |
58 | /* There can only be one writer at a time, but this needs to be */ |
59 | /* atomic with respect to concurrent readers. */ | |
60 | *(volatile tse **)(key -> hash + hash_val) = entry; | |
61 | pthread_mutex_unlock(&(key -> lock)); | |
62 | return 0; | |
63 | } | |
64 | ||
65 | /* Remove thread-specific data for this thread. Should be called on */ | |
66 | /* thread exit. */ | |
67 | void PREFIXED(remove_specific) (tsd * key) { | |
68 | pthread_t self = pthread_self(); | |
69 | unsigned hash_val = HASH(self); | |
70 | tse *entry; | |
71 | tse **link = key -> hash + hash_val; | |
72 | ||
73 | pthread_mutex_lock(&(key -> lock)); | |
74 | entry = *link; | |
75 | while (entry != NULL && entry -> thread != self) { | |
76 | link = &(entry -> next); | |
77 | entry = *link; | |
78 | } | |
79 | /* Invalidate qtid field, since qtids may be reused, and a later */ | |
80 | /* cache lookup could otherwise find this entry. */ | |
81 | entry -> qtid = INVALID_QTID; | |
82 | if (entry != NULL) { | |
83 | *link = entry -> next; | |
84 | /* Atomic! concurrent accesses still work. */ | |
85 | /* They must, since readers don't lock. */ | |
4d6ac542 HB |
86 | /* We shouldn't need a volatile access here, */ |
87 | /* since both this and the preceding write */ | |
88 | /* should become visible no later than */ | |
89 | /* the pthread_mutex_unlock() call. */ | |
f54d4287 BM |
90 | } |
91 | /* If we wanted to deallocate the entry, we'd first have to clear */ | |
92 | /* any cache entries pointing to it. That probably requires */ | |
93 | /* additional synchronization, since we can't prevent a concurrent */ | |
94 | /* cache lookup, which should still be examining deallocated memory.*/ | |
95 | /* This can only happen if the concurrent access is from another */ | |
96 | /* thread, and hence has missed the cache, but still... */ | |
97 | ||
98 | /* With GC, we're done, since the pointers from the cache will */ | |
99 | /* be overwritten, all local pointers to the entries will be */ | |
100 | /* dropped, and the entry will then be reclaimed. */ | |
101 | pthread_mutex_unlock(&(key -> lock)); | |
102 | } | |
103 | ||
104 | /* Note that even the slow path doesn't lock. */ | |
105 | void * PREFIXED(slow_getspecific) (tsd * key, unsigned long qtid, | |
106 | tse * volatile * cache_ptr) { | |
107 | pthread_t self = pthread_self(); | |
108 | unsigned hash_val = HASH(self); | |
109 | tse *entry = key -> hash[hash_val]; | |
110 | ||
4d6ac542 | 111 | GC_ASSERT(qtid != INVALID_QTID); |
f54d4287 BM |
112 | while (entry != NULL && entry -> thread != self) { |
113 | entry = entry -> next; | |
114 | } | |
115 | if (entry == NULL) return NULL; | |
116 | /* Set cache_entry. */ | |
117 | entry -> qtid = qtid; | |
118 | /* It's safe to do this asynchronously. Either value */ | |
119 | /* is safe, though may produce spurious misses. */ | |
4d6ac542 HB |
120 | /* We're replacing one qtid with another one for the */ |
121 | /* same thread. */ | |
f54d4287 BM |
122 | *cache_ptr = entry; |
123 | /* Again this is safe since pointer assignments are */ | |
124 | /* presumed atomic, and either pointer is valid. */ | |
125 | return entry -> value; | |
126 | } | |
127 | ||
4c7726b1 | 128 | #endif /* GC_LINUX_THREADS */ |