#ifndef _HASH_H #define _HASH_H #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <php.h> #include <zend_types.h> typedef unsigned long int ulong; typedef struct string_t { unsigned int len; char str[1]; } string_t; typedef enum { NULL_T=0, BOOL_T, LONG_T, DOUBLE_T, STR_T, HT_T, SERI_T, TS_HT_T } type_t; typedef struct value_t { type_t type; int expire; union { zend_bool b; long int l; double d; string_t *str; void *ptr; }; } value_t; typedef struct bucket_t { ulong h; /* Used for numeric indexing */ value_t value; struct bucket_t *pListNext; struct bucket_t *pListLast; struct bucket_t *pNext; struct bucket_t *pLast; uint nKeyLength; char arKey[1]; } bucket_t; typedef void (*hash_dtor_func_t)(value_t *pDest); typedef struct hash_table_t { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; bucket_t *pListHead; bucket_t *pListTail; bucket_t **arBuckets; hash_dtor_func_t pDestructor; } hash_table_t; void hash_table_value_free(value_t *value); /* startup/shutdown */ int _hash_table_init(hash_table_t *ht, uint nSize, hash_dtor_func_t pDestructor); void hash_table_destroy(hash_table_t *ht); void hash_table_clean(hash_table_t *ht); #define hash_table_init(ht, nSize) _hash_table_init((ht), (nSize), hash_table_value_free) #define hash_table_init_ex(ht, nSize, pDestructor) _hash_table_init((ht), (nSize), (pDestructor)) #define HASH_TABLE_ADD 1 #define HASH_TABLE_UPDATE 2 #define HASH_TABLE_NEXT_INSERT 4 /* additions/updates/changes */ int _hash_table_add_or_update(hash_table_t *ht, const char *arKey, uint nKeyLength, value_t *pData, int flag); #define hash_table_update(ht, arKey, nKeyLength, pData, pDest) \ _hash_table_add_or_update(ht, arKey, nKeyLength, pData, HASH_TABLE_UPDATE) #define hash_table_add(ht, arKey, nKeyLength, pData, pDest) \ _hash_table_add_or_update(ht, arKey, nKeyLength, pData, HASH_TABLE_ADD) int _hash_table_quick_add_or_update(hash_table_t *ht, const char *arKey, uint nKeyLength, ulong h, value_t *pData, int flag); #define hash_table_quick_update(ht, arKey, nKeyLength, h, pData, pDest) \ _hash_table_quick_add_or_update(ht, arKey, nKeyLength, h, pData, HASH_TABLE_UPDATE) #define hash_table_quick_add(ht, arKey, nKeyLength, h, pData, pDest) \ _hash_table_quick_add_or_update(ht, arKey, nKeyLength, h, pData, HASH_TABLE_ADD) int _hash_table_index_update_or_next_insert(hash_table_t *ht, ulong h, value_t *pData, int flag); #define hash_table_index_update(ht, h, pData, pDest) \ _hash_table_index_update_or_next_insert(ht, h, pData, HASH_TABLE_UPDATE) #define hash_table_next_index_insert(ht, pData, pDest) \ _hash_table_index_update_or_next_insert(ht, 0, pData, HASH_TABLE_NEXT_INSERT) #define HASH_TABLE_APPLY_KEEP 1 #define HASH_TABLE_APPLY_REMOVE 2 #define HASH_TABLE_APPLY_STOP 4 typedef int (*hash_apply_func_t)(bucket_t *pDest); typedef int (*hash_apply_func_arg_t)(bucket_t *pDest, void *argument); typedef int (*hash_apply_func_args_t)(bucket_t *pDest, int num_args, va_list args); void hash_table_apply(hash_table_t *ht, hash_apply_func_t apply_func); void hash_table_apply_with_argument(hash_table_t *ht, hash_apply_func_arg_t apply_func, void *); void hash_table_apply_with_arguments(hash_table_t *ht, hash_apply_func_args_t apply_func, int, ...); #define HASH_TABLE_DEL_KEY 1 #define HASH_TABLE_DEL_KEY_QUICK 2 #define HASH_TABLE_DEL_INDEX 4 /* Deletes */ int hash_table_del_key_or_index(hash_table_t *ht, const char *arKey, uint nKeyLength, ulong h, int flag); #define hash_table_del(ht, arKey, nKeyLength) \ hash_table_del_key_or_index(ht, arKey, nKeyLength, 0, HASH_TABLE_DEL_KEY) #define hash_table_quick_del(ht, arKey, nKeyLength, h) \ hash_table_del_key_or_index(ht, arKey, nKeyLength, h, HASH_TABLE_DEL_KEY_QUICK) #define hash_table_index_del(ht, h) \ hash_table_del_key_or_index(ht, NULL, 0, h, HASH_TABLE_DEL_INDEX) #define zend_get_hash_value(s,l) hash_table_func(s,l) /* Data retreival */ int hash_table_find(const hash_table_t *ht, const char *arKey, uint nKeyLength, value_t *pData); int hash_table_quick_find(const hash_table_t *ht, const char *arKey, uint nKeyLength, ulong h, value_t *pData); int hash_table_index_find(const hash_table_t *ht, ulong h, value_t *pData); /* Misc */ int hash_table_exists(const hash_table_t *ht, const char *arKey, uint nKeyLength); int hash_table_quick_exists(const hash_table_t *ht, const char *arKey, uint nKeyLength, ulong h); int hash_table_index_exists(const hash_table_t *ht, ulong h); ulong hash_table_next_free_element(const hash_table_t *ht); int hash_table_num_elements(const hash_table_t *ht); void hash_table_reindex(hash_table_t *ht, zend_bool only_integer_keys); ulong hash_table_func(const char *arKey, uint nKeyLength); static zend_always_inline void hash_table_bucket_delete(hash_table_t *ht, bucket_t *p) { if (p->pLast) { p->pLast->pNext = p->pNext; } else { ht->arBuckets[p->h & ht->nTableMask] = p->pNext; } if (p->pNext) { p->pNext->pLast = p->pLast; } if (p->pListLast != NULL) { p->pListLast->pListNext = p->pListNext; } else { /* Deleting the head of the list */ ht->pListHead = p->pListNext; } if (p->pListNext != NULL) { p->pListNext->pListLast = p->pListLast; } else { /* Deleting the tail of the list */ ht->pListTail = p->pListLast; } ht->nNumOfElements--; if (ht->pDestructor) { ht->pDestructor(&p->value); } free(p); } typedef int (*hash_compare_func_t)(const bucket_t *a, const bucket_t *b); int compare_key(const bucket_t *a, const bucket_t *b); int compare_value(const bucket_t *a, const bucket_t *b); int hash_table_minmax(const hash_table_t *ht, hash_compare_func_t compar, int flag, bucket_t **ret); int hash_table_sort(hash_table_t *ht, hash_compare_func_t compar, int reverse); // =========================================================================================================== #include <pthread.h> #include <time.h> #include <zend_exceptions.h> #if defined(LOCK_TIMEOUT) && LOCK_TIMEOUT <= 0 #undef LOCK_TIMEOUT #endif #ifdef LOCK_TIMEOUT typedef struct _tskey_hash_table_t { hash_table_t ht; pthread_mutex_t lock; } tskey_hash_table_t; #endif typedef struct ts_hash_table_t { hash_table_t ht; int expire; volatile unsigned short rd_count; volatile unsigned short ref_count; // reference count pthread_mutex_t wlock; pthread_mutex_t rlock; int fds[2]; #ifdef LOCK_TIMEOUT tskey_hash_table_t *tsht; ulong h; #endif } ts_hash_table_t; static zend_always_inline int _ts_hash_table_init(ts_hash_table_t *ts_ht, uint nSize, hash_dtor_func_t pDestructor) { ts_ht->rd_count = 0; ts_ht->ref_count = 1; ts_ht->fds[0] = 0; ts_ht->fds[1] = 0; ts_ht->expire = 0; #ifdef LOCK_TIMEOUT ts_ht->tsht = NULL; ts_ht->h = hash_table_func((const char*) &ts_ht, sizeof(void*)); #endif pthread_mutex_init(&ts_ht->rlock, NULL); pthread_mutex_init(&ts_ht->wlock, NULL); return _hash_table_init(&ts_ht->ht, nSize, pDestructor); } #define ts_hash_table_init(ht, nSize) _ts_hash_table_init((ht), (nSize), hash_table_value_free) #define ts_hash_table_init_ex(ht, nSize, pDestructor) _ts_hash_table_init((ht), (nSize), (pDestructor)) static zend_always_inline void ts_hash_table_lock(ts_hash_table_t *ts_ht) { pthread_mutex_lock(&ts_ht->rlock); } static zend_always_inline void ts_hash_table_unlock(ts_hash_table_t *ts_ht) { pthread_mutex_unlock(&ts_ht->rlock); } #ifdef LOCK_TIMEOUT extern pthread_key_t tskey; void ts_table_table_tid_destroy(void *hh); long int ts_table_table_tid_inc(ts_hash_table_t *hh); long int ts_table_table_tid_dec_ex(tskey_hash_table_t *tsht, ts_hash_table_t *hh); #define ts_table_table_tid_dec(ht) ts_table_table_tid_dec_ex(pthread_getspecific(tskey), ht) const char *gettimeofstr(); void ts_hash_table_deadlock(const char *msg); #endif static zend_always_inline void ts_hash_table_wr_lock(ts_hash_table_t *ts_ht) { #ifndef LOCK_TIMEOUT pthread_mutex_lock(&ts_ht->wlock); #else // printf("%p lock\n", ts_ht); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += LOCK_TIMEOUT; if(pthread_mutex_timedlock(&ts_ht->wlock, &ts)) { if(ts_table_table_tid_inc(ts_ht) == 1) { ts_hash_table_deadlock("Deadlock caused by shared variables"); } else { ts_hash_table_deadlock("Repeated locking of shared variables"); } pthread_mutex_lock(&ts_ht->wlock); } else { ts_table_table_tid_inc(ts_ht); } ts_ht->tsht = pthread_getspecific(tskey); #endif } static zend_always_inline void ts_hash_table_wr_unlock(ts_hash_table_t *ts_ht) { #ifndef LOCK_TIMEOUT pthread_mutex_unlock(&ts_ht->wlock); #else long int i = ts_table_table_tid_dec(ts_ht); if(i == 0) { ts_ht->tsht = NULL; // printf("%p unlock\n", ts_ht); pthread_mutex_unlock(&ts_ht->wlock); } else if(i < 0) { i = ts_table_table_tid_dec_ex(ts_ht->tsht, ts_ht); if(i == 0) { ts_ht->tsht = NULL; // printf("%p unlock\n", ts_ht); pthread_mutex_unlock(&ts_ht->wlock); } else if(i < 0) { ts_hash_table_deadlock("Shared variable unlocking exception"); } } #endif } static zend_always_inline void ts_hash_table_rd_lock(ts_hash_table_t *ts_ht) { pthread_mutex_lock(&ts_ht->rlock); if((++ts_ht->rd_count) == 1) ts_hash_table_wr_lock(ts_ht); pthread_mutex_unlock(&ts_ht->rlock); } static zend_always_inline void ts_hash_table_rd_unlock(ts_hash_table_t *ts_ht) { pthread_mutex_lock(&ts_ht->rlock); if((--ts_ht->rd_count) == 0) ts_hash_table_wr_unlock(ts_ht); pthread_mutex_unlock(&ts_ht->rlock); } static zend_always_inline void ts_hash_table_ref(ts_hash_table_t *ts_ht) { ts_hash_table_lock(ts_ht); ts_ht->ref_count++; ts_hash_table_unlock(ts_ht); } #define ts_hash_table_unref(ts_ht) ts_hash_table_destroy(ts_ht) static zend_always_inline void ts_hash_table_destroy_ex(ts_hash_table_t *ts_ht, int is_free) { ts_hash_table_lock(ts_ht); if(--ts_ht->ref_count == 0) { ts_hash_table_unlock(ts_ht); if(ts_ht->fds[0] > 0) close(ts_ht->fds[0]); if(ts_ht->fds[1] > 0) close(ts_ht->fds[1]); pthread_mutex_destroy(&ts_ht->rlock); pthread_mutex_destroy(&ts_ht->wlock); hash_table_destroy(&ts_ht->ht); if(is_free) free(ts_ht); } else ts_hash_table_unlock(ts_ht); } #define ts_hash_table_destroy(ts_ht) ts_hash_table_destroy_ex(ts_ht, 1) #endif /* _HASH_H */