1999-04-23 19:00:02 +08:00
|
|
|
/*
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| Thread Safe Resource Manager |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| Copyright (c) 1998, 1999 Zeev Suraski |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| This source file is subject to the Zend license, that is bundled |
|
|
|
|
| with this package in the file LICENSE. If you did not receive a |
|
|
|
|
| copy of the Zend license, please mail us at zend@zend.com so we can |
|
|
|
|
| send you a copy immediately. |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
| Author: Zeev Suraski <zeev@zend.com> |
|
|
|
|
+----------------------------------------------------------------------+
|
|
|
|
*/
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
#include "TSRM.h"
|
|
|
|
#include <stdio.h>
|
1999-05-07 05:13:26 +08:00
|
|
|
#include <stdlib.h>
|
1999-04-21 07:58:02 +08:00
|
|
|
|
1999-04-24 17:01:30 +08:00
|
|
|
#if HAVE_STDARG_H
|
|
|
|
#include <stdarg.h>
|
|
|
|
#endif
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
typedef struct _tsrm_tls_entry tsrm_tls_entry;
|
|
|
|
|
|
|
|
struct _tsrm_tls_entry {
|
|
|
|
void **storage;
|
|
|
|
int count;
|
|
|
|
THREAD_T thread_id;
|
|
|
|
tsrm_tls_entry *next;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
size_t size;
|
1999-12-06 00:21:37 +08:00
|
|
|
ts_allocate_ctor ctor;
|
|
|
|
ts_allocate_dtor dtor;
|
1999-04-21 07:58:02 +08:00
|
|
|
} tsrm_resource_type;
|
|
|
|
|
|
|
|
|
|
|
|
/* The memory manager table */
|
|
|
|
static tsrm_tls_entry **tsrm_tls_table;
|
|
|
|
static int tsrm_tls_table_size;
|
|
|
|
static ts_rsrc_id id_count;
|
|
|
|
|
|
|
|
/* The resource sizes table */
|
|
|
|
static tsrm_resource_type *resource_types_table;
|
|
|
|
static int resource_types_table_size;
|
|
|
|
|
|
|
|
|
|
|
|
static MUTEX_T tsmm_mutex; /* thread-safe memory manager mutex */
|
|
|
|
|
1999-08-14 17:35:52 +08:00
|
|
|
/* New thread handlers */
|
|
|
|
static void (*tsrm_new_thread_begin_handler)();
|
|
|
|
static void (*tsrm_new_thread_end_handler)();
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
/* Debug support */
|
|
|
|
static int tsrm_debug(const char *format, ...);
|
|
|
|
static int tsrm_debug_status;
|
|
|
|
|
|
|
|
|
|
|
|
/* Startup TSRM (call once for the entire process) */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_status)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
1999-12-06 05:55:43 +08:00
|
|
|
#if defined(GNUPTH)
|
|
|
|
pth_init();
|
|
|
|
#endif
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
tsrm_tls_table_size = expected_threads;
|
|
|
|
tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *));
|
|
|
|
if (!tsrm_tls_table) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
id_count=0;
|
|
|
|
|
|
|
|
resource_types_table_size = expected_resources;
|
|
|
|
resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type));
|
|
|
|
if (!resource_types_table) {
|
|
|
|
free(tsrm_tls_table);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
tsmm_mutex = tsrm_mutex_alloc();
|
|
|
|
|
1999-08-14 17:35:52 +08:00
|
|
|
tsrm_new_thread_begin_handler = tsrm_new_thread_end_handler = NULL;
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
tsrm_debug_status = debug_status;
|
|
|
|
|
|
|
|
tsrm_debug("Started up TSRM, %d expected threads, %d expected resources\n", expected_threads, expected_resources);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Shutdown TSRM (call once for the entire process) */
|
1999-12-06 00:21:37 +08:00
|
|
|
TSRM_API void tsrm_shutdown(void)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (tsrm_tls_table) {
|
|
|
|
for (i=0; i<tsrm_tls_table_size; i++) {
|
|
|
|
tsrm_tls_entry *p = tsrm_tls_table[i], *next_p;
|
|
|
|
|
|
|
|
while (p) {
|
|
|
|
int j;
|
|
|
|
|
|
|
|
next_p = p->next;
|
|
|
|
for (j=0; j<id_count; j++) {
|
|
|
|
free(p->storage[j]);
|
|
|
|
}
|
|
|
|
free(p->storage);
|
|
|
|
free(p);
|
|
|
|
p = next_p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(tsrm_tls_table);
|
|
|
|
}
|
|
|
|
if (resource_types_table) {
|
|
|
|
free(resource_types_table);
|
|
|
|
}
|
|
|
|
tsrm_mutex_free(tsmm_mutex);
|
|
|
|
tsrm_debug("Shutdown TSRM\n");
|
1999-12-06 05:55:43 +08:00
|
|
|
#if defined(GNUPTH)
|
|
|
|
pth_kill();
|
|
|
|
#endif
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* allocates a new thread-safe-resource id */
|
1999-12-06 00:21:37 +08:00
|
|
|
TSRM_API ts_rsrc_id ts_allocate_id(size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
ts_rsrc_id new_id;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
tsrm_debug("Obtaining a new resource id, %d bytes\n", size);
|
|
|
|
|
|
|
|
tsrm_mutex_lock(tsmm_mutex);
|
|
|
|
|
|
|
|
/* obtain a resource id */
|
|
|
|
new_id = id_count++;
|
|
|
|
tsrm_debug("Obtained resource id %d\n", new_id);
|
|
|
|
|
|
|
|
/* store the new resource type in the resource sizes table */
|
|
|
|
if (resource_types_table_size < id_count) {
|
|
|
|
resource_types_table = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count);
|
|
|
|
if (!resource_types_table) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
resource_types_table_size = id_count;
|
|
|
|
}
|
|
|
|
resource_types_table[new_id].size = size;
|
|
|
|
resource_types_table[new_id].ctor = ctor;
|
|
|
|
resource_types_table[new_id].dtor = dtor;
|
|
|
|
|
|
|
|
/* enlarge the arrays for the already active threads */
|
|
|
|
for (i=0; i<tsrm_tls_table_size; i++) {
|
|
|
|
tsrm_tls_entry *p = tsrm_tls_table[i];
|
|
|
|
|
|
|
|
while (p) {
|
|
|
|
if (p->count < id_count) {
|
|
|
|
int j;
|
|
|
|
|
1999-04-24 17:01:30 +08:00
|
|
|
p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count);
|
1999-04-21 07:58:02 +08:00
|
|
|
for (j=p->count; j<id_count; j++) {
|
|
|
|
p->storage[j] = (void *) malloc(resource_types_table[j].size);
|
1999-04-23 19:00:02 +08:00
|
|
|
if (resource_types_table[j].ctor) {
|
|
|
|
resource_types_table[j].ctor(p->storage[j]);
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
|
|
|
p->count = id_count;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tsrm_mutex_unlock(tsmm_mutex);
|
|
|
|
|
|
|
|
tsrm_debug("Successfully allocated new resource id %d\n", new_id);
|
|
|
|
return new_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
(*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry));
|
|
|
|
(*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count);
|
|
|
|
(*thread_resources_ptr)->count = id_count;
|
|
|
|
(*thread_resources_ptr)->thread_id = thread_id;
|
|
|
|
(*thread_resources_ptr)->next = NULL;
|
1999-10-10 21:30:03 +08:00
|
|
|
|
|
|
|
tsrm_mutex_unlock(tsmm_mutex);
|
|
|
|
|
|
|
|
if (tsrm_new_thread_begin_handler) {
|
|
|
|
tsrm_new_thread_begin_handler(thread_id);
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
for (i=0; i<id_count; i++) {
|
|
|
|
(*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size);
|
1999-04-23 19:00:02 +08:00
|
|
|
if (resource_types_table[i].ctor) {
|
|
|
|
resource_types_table[i].ctor((*thread_resources_ptr)->storage[i]);
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
1999-08-14 17:35:52 +08:00
|
|
|
if (tsrm_new_thread_end_handler) {
|
|
|
|
tsrm_new_thread_end_handler(thread_id);
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* fetches the requested resource for the current thread */
|
2000-06-17 22:56:19 +08:00
|
|
|
TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
2000-06-16 22:38:05 +08:00
|
|
|
THREAD_T thread_id;
|
1999-04-21 07:58:02 +08:00
|
|
|
int hash_value;
|
|
|
|
tsrm_tls_entry *thread_resources;
|
|
|
|
void *resource;
|
|
|
|
|
2000-06-16 22:38:05 +08:00
|
|
|
if (th_id) {
|
|
|
|
thread_id = *th_id;
|
|
|
|
} else {
|
|
|
|
thread_id = tsrm_thread_id();
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
tsrm_debug("Fetching resource id %d for thread %ld\n", id, (long) thread_id);
|
|
|
|
tsrm_mutex_lock(tsmm_mutex);
|
|
|
|
|
|
|
|
hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);
|
|
|
|
thread_resources = tsrm_tls_table[hash_value];
|
|
|
|
|
|
|
|
if (!thread_resources) {
|
|
|
|
allocate_new_resource(&tsrm_tls_table[hash_value], thread_id);
|
1999-10-10 21:30:03 +08:00
|
|
|
return ts_resource(id);
|
|
|
|
/* thread_resources = tsrm_tls_table[hash_value]; */
|
1999-04-21 07:58:02 +08:00
|
|
|
} else {
|
|
|
|
do {
|
|
|
|
if (thread_resources->thread_id == thread_id) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (thread_resources->next) {
|
|
|
|
thread_resources = thread_resources->next;
|
|
|
|
} else {
|
|
|
|
allocate_new_resource(&thread_resources->next, thread_id);
|
1999-10-10 21:30:03 +08:00
|
|
|
return ts_resource(id);
|
|
|
|
/*
|
|
|
|
* thread_resources = thread_resources->next;
|
|
|
|
* break;
|
|
|
|
*/
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
|
|
|
} while (thread_resources);
|
|
|
|
}
|
|
|
|
|
|
|
|
resource = thread_resources->storage[id];
|
|
|
|
|
|
|
|
tsrm_mutex_unlock(tsmm_mutex);
|
|
|
|
|
|
|
|
tsrm_debug("Successfully fetched resource id %d for thread id %ld - %x\n", id, (long) thread_id, (long) resource);
|
|
|
|
return resource;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* frees all resources allocated for the current thread */
|
1999-12-06 00:21:37 +08:00
|
|
|
void ts_free_thread(void)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
THREAD_T thread_id = tsrm_thread_id();
|
|
|
|
int hash_value;
|
|
|
|
tsrm_tls_entry *thread_resources;
|
|
|
|
tsrm_tls_entry *last=NULL;
|
|
|
|
|
|
|
|
tsrm_mutex_lock(tsmm_mutex);
|
|
|
|
hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);
|
|
|
|
thread_resources = tsrm_tls_table[hash_value];
|
|
|
|
|
|
|
|
while (thread_resources) {
|
|
|
|
if (thread_resources->thread_id == thread_id) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i=0; i<thread_resources->count; i++) {
|
1999-04-23 19:00:02 +08:00
|
|
|
if (resource_types_table[i].dtor) {
|
|
|
|
resource_types_table[i].dtor(thread_resources->storage[i]);
|
|
|
|
}
|
1999-04-21 07:58:02 +08:00
|
|
|
free(thread_resources->storage[i]);
|
|
|
|
}
|
|
|
|
free(thread_resources->storage);
|
|
|
|
if (last) {
|
|
|
|
last->next = thread_resources->next;
|
|
|
|
} else {
|
|
|
|
tsrm_tls_table[hash_value]=NULL;
|
|
|
|
}
|
|
|
|
free(thread_resources);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (thread_resources->next) {
|
|
|
|
last = thread_resources;
|
|
|
|
}
|
1999-05-12 05:27:44 +08:00
|
|
|
thread_resources = thread_resources->next;
|
1999-04-21 07:58:02 +08:00
|
|
|
}
|
|
|
|
tsrm_mutex_unlock(tsmm_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* deallocates all occurrences of a given id */
|
|
|
|
void ts_free_id(ts_rsrc_id id)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Utility Functions
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Obtain the current thread id */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API THREAD_T tsrm_thread_id(void)
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
#ifdef WIN32
|
|
|
|
return GetCurrentThreadId();
|
1999-12-06 05:55:43 +08:00
|
|
|
#elif defined(GNUPTH)
|
|
|
|
return pth_self();
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(PTHREADS)
|
|
|
|
return pthread_self();
|
|
|
|
#elif defined(NSAPI)
|
|
|
|
return systhread_current();
|
|
|
|
#elif defined(PI3WEB)
|
|
|
|
return PIThread_getCurrent();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Allocate a mutex */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API MUTEX_T tsrm_mutex_alloc( void )
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
MUTEX_T mutexp;
|
|
|
|
|
|
|
|
#ifdef WIN32
|
1999-12-21 04:02:25 +08:00
|
|
|
mutexp = malloc(sizeof(CRITICAL_SECTION));
|
|
|
|
InitializeCriticalSection(mutexp);
|
1999-12-06 05:55:43 +08:00
|
|
|
#elif defined(GNUPTH)
|
|
|
|
mutexp = (MUTEX_T) malloc(sizeof(*mutexp));
|
|
|
|
pth_mutex_init(mutexp);
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(PTHREADS)
|
|
|
|
mutexp = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
|
|
|
|
pthread_mutex_init(mutexp,NULL);
|
|
|
|
#elif defined(NSAPI)
|
|
|
|
mutexp = crit_init();
|
|
|
|
#elif defined(PI3WEB)
|
|
|
|
mutexp = PIPlatform_allocLocalMutex();
|
|
|
|
#endif
|
|
|
|
#ifdef THR_DEBUG
|
|
|
|
printf("Mutex created thread: %d\n",mythreadid());
|
|
|
|
#endif
|
|
|
|
return( mutexp );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Free a mutex */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API void tsrm_mutex_free( MUTEX_T mutexp )
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
|
|
|
if (mutexp) {
|
|
|
|
#ifdef WIN32
|
1999-12-21 04:02:25 +08:00
|
|
|
DeleteCriticalSection(mutexp);
|
1999-12-06 05:55:43 +08:00
|
|
|
#elif defined(GNUPTH)
|
|
|
|
free(mutexp);
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(PTHREADS)
|
1999-11-26 23:33:42 +08:00
|
|
|
pthread_mutex_destroy(mutexp);
|
1999-12-06 05:55:43 +08:00
|
|
|
free(mutexp);
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(NSAPI)
|
|
|
|
crit_terminate(mutexp);
|
|
|
|
#elif defined(PI3WEB)
|
|
|
|
PISync_delete(mutexp)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#ifdef THR_DEBUG
|
|
|
|
printf("Mutex freed thread: %d\n",mythreadid());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Lock a mutex */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API int tsrm_mutex_lock( MUTEX_T mutexp )
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
1999-09-27 23:37:00 +08:00
|
|
|
#if 0
|
|
|
|
tsrm_debug("Mutex locked thread: %ld\n",tsrm_thread_id());
|
|
|
|
#endif
|
1999-04-21 07:58:02 +08:00
|
|
|
#ifdef WIN32
|
1999-12-21 04:02:25 +08:00
|
|
|
EnterCriticalSection(mutexp);
|
|
|
|
return 1;
|
1999-12-06 05:55:43 +08:00
|
|
|
#elif defined(GNUPTH)
|
|
|
|
return pth_mutex_acquire(mutexp, 0, NULL);
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(PTHREADS)
|
|
|
|
return pthread_mutex_lock(mutexp);
|
|
|
|
#elif defined(NSAPI)
|
|
|
|
return crit_enter(mutexp);
|
|
|
|
#elif defined(PI3WEB)
|
|
|
|
return PISync_lock(mutexp);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Unlock a mutex */
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API int tsrm_mutex_unlock( MUTEX_T mutexp )
|
1999-04-21 07:58:02 +08:00
|
|
|
{
|
1999-09-27 23:37:00 +08:00
|
|
|
#if 0
|
|
|
|
tsrm_debug("Mutex unlocked thread: %ld\n",tsrm_thread_id());
|
|
|
|
#endif
|
1999-04-21 07:58:02 +08:00
|
|
|
#ifdef WIN32
|
1999-12-21 04:02:25 +08:00
|
|
|
LeaveCriticalSection(mutexp);
|
|
|
|
return 1;
|
1999-12-06 05:55:43 +08:00
|
|
|
#elif defined(GNUPTH)
|
|
|
|
return pth_mutex_release(mutexp);
|
1999-04-21 07:58:02 +08:00
|
|
|
#elif defined(PTHREADS)
|
|
|
|
return pthread_mutex_unlock(mutexp);
|
|
|
|
#elif defined(NSAPI)
|
|
|
|
return crit_exit(mutexp);
|
|
|
|
#elif defined(PI3WEB)
|
|
|
|
return PISync_unlock(mutexp);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-08-14 17:35:52 +08:00
|
|
|
TSRM_API void *tsrm_set_new_thread_begin_handler(void (*new_thread_begin_handler)(THREAD_T thread_id))
|
|
|
|
{
|
|
|
|
void *retval = (void *) tsrm_new_thread_begin_handler;
|
|
|
|
|
|
|
|
tsrm_new_thread_begin_handler = new_thread_begin_handler;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TSRM_API void *tsrm_set_new_thread_end_handler(void (*new_thread_end_handler)(THREAD_T thread_id))
|
|
|
|
{
|
|
|
|
void *retval = (void *) tsrm_new_thread_end_handler;
|
|
|
|
|
|
|
|
tsrm_new_thread_end_handler = new_thread_end_handler;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
1999-04-21 07:58:02 +08:00
|
|
|
/*
|
|
|
|
* Debug support
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int tsrm_debug(const char *format, ...)
|
|
|
|
{
|
|
|
|
if (tsrm_debug_status) {
|
|
|
|
va_list args;
|
|
|
|
int size;
|
|
|
|
|
|
|
|
va_start(args, format);
|
|
|
|
size = vprintf(format, args);
|
|
|
|
va_end(args);
|
|
|
|
return size;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void tsrm_debug_set(int status)
|
|
|
|
{
|
|
|
|
tsrm_debug_status = status;
|
1999-04-24 17:01:30 +08:00
|
|
|
}
|