8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 06:03:02 +01:00

Refactor xnet module to separate client and server parts (endpoints).

It fixed bug CORE-5029 : AV in fbclient when multi-threaded application uses XNET protocol
and allows in the future to implement client ability to specify target server endpoint name 
at connection level (currently it is set in firebird.conf).
This commit is contained in:
hvlad 2015-11-23 16:06:10 +00:00
parent c32ac809ad
commit 14fb40a597
2 changed files with 170 additions and 113 deletions

View File

@ -51,6 +51,7 @@
#endif // WIN_NT
using namespace Firebird;
using namespace Remote;
static bool accept_connection(rem_port*, const P_CNCT*);
static rem_port* alloc_port(rem_port*, UCHAR*, ULONG, UCHAR*, ULONG);
@ -58,10 +59,7 @@ static rem_port* aux_connect(rem_port*, PACKET*);
static rem_port* aux_request(rem_port*, PACKET*);
static void cleanup_comm(XCC);
static void cleanup_mapping(XPM);
static void cleanup_port(rem_port*);
static rem_port* connect_client(PACKET*, const Firebird::RefPtr<Config>*);
static rem_port* connect_server(USHORT);
static void disconnect(rem_port*);
static void force_close(rem_port*);
static int cleanup_ports(const int, const int, void* arg);
@ -70,14 +68,6 @@ static rem_port* receive(rem_port*, PACKET*);
static int send_full(rem_port*, PACKET*);
static int send_partial(rem_port*, PACKET*);
static void server_shutdown(rem_port* port);
static rem_port* get_server_port(ULONG, XPM, ULONG, ULONG, ULONG);
static void make_map(ULONG, ULONG, FILE_ID*, CADDR_T*);
static XPM make_xpm(ULONG, ULONG);
static bool server_init(USHORT);
static XPM get_free_slot(ULONG*, ULONG*, ULONG*);
static bool fork(ULONG, USHORT, ULONG*);
static int xdrxnet_create(XDR*, rem_port*, UCHAR*, USHORT, xdr_op);
static bool_t xnet_getbytes(XDR*, SCHAR*, u_int);
@ -91,74 +81,131 @@ static xdr_t::xdr_ops xnet_ops =
xnet_putbytes
};
static ULONG global_pages_per_slot = XPS_DEF_PAGES_PER_CLI;
static ULONG global_slots_per_map = XPS_DEF_NUM_CLI;
static XPM global_client_maps = NULL;
#ifdef WIN_NT
static HANDLE xnet_connect_mutex = 0;
static HANDLE xnet_connect_map_h = 0;
static CADDR_T xnet_connect_map = 0;
static HANDLE xnet_connect_event = 0;
static HANDLE xnet_response_event = 0;
static DWORD current_process_id;
// XNET endpoint is the IPC prefix name used to access the server.
// It may have to be dynamically determined and has to be initialized
// before the protocol can be used. It is initialized at the following points:
// - XNET_reconnect (classic servant size)
// - connect_client (client side)
// - server_init (listener side)
static char xnet_endpoint[BUFFER_TINY] = "";
#endif // WIN_NT
static volatile bool xnet_initialized = false;
static volatile bool xnet_shutdown = false;
static GlobalPtr<Mutex> xnet_mutex;
static GlobalPtr<PortsCleanup> xnet_ports;
static ULONG xnet_next_free_map_num = 0;
static bool connect_init();
static void connect_fini();
static void release_all();
namespace
namespace Remote
{
class ExitHandler
class XnetEndPoint
{
public:
explicit ExitHandler(MemoryPool&) {};
~ExitHandler()
XnetEndPoint()
{
xnet_shutdown = true;
release_all();
xnet_initialized = false;
xnet_connect_mutex = 0;
xnet_connect_map_h = 0;
xnet_connect_map = NULL;
xnet_connect_event = 0;
xnet_response_event = 0;
global_client_maps = NULL;
xnet_endpoint[0] = 0;
}
~XnetEndPoint()
{
shutdown();
}
void make_obj_name(char* buffer, size_t size, const char* format);
void make_map_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2);
void make_event_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2, ULONG arg3);
void connect_fini();
void cleanup_mapping(XPM);
void shutdown();
bool xnet_initialized;
Mutex xnet_mutex;
HANDLE xnet_connect_mutex;
HANDLE xnet_connect_map_h;
CADDR_T xnet_connect_map;
HANDLE xnet_connect_event;
HANDLE xnet_response_event;
XPM global_client_maps;
// XNET endpoint is the IPC prefix name used to access the server.
// It may have to be dynamically determined and has to be initialized
// before the protocol can be used. It is initialized at the following points:
// - XNET_reconnect (classic servant size)
// - connect_client (client side)
// - server_init (listener side)
char xnet_endpoint[BUFFER_TINY];
};
static GlobalPtr<ExitHandler> xnet_exit_handler;
}
class XnetClientEndPoint : public XnetEndPoint
{
public:
explicit XnetClientEndPoint(const MemoryPool&) : XnetEndPoint()
{
}
~XnetClientEndPoint()
{
}
rem_port* connect_client(PACKET*, const RefPtr<Config>*);
void server_shutdown(rem_port* port);
private:
bool connect_init();
};
class XnetServerEndPoint : public XnetEndPoint
{
public:
explicit XnetServerEndPoint(const MemoryPool&) : XnetEndPoint()
{
global_pages_per_slot = XPS_DEF_PAGES_PER_CLI;
global_slots_per_map = XPS_DEF_NUM_CLI;
xnet_next_free_map_num = 0;
}
~XnetServerEndPoint()
{
xnet_shutdown = true;
}
rem_port* connect_server(USHORT);
rem_port* reconnect(ULONG client_pid);
private:
void make_map(ULONG, ULONG, FILE_ID*, CADDR_T*);
XPM make_xpm(ULONG, ULONG);
bool server_init(USHORT);
XPM get_free_slot(ULONG*, ULONG*, ULONG*);
bool fork(ULONG, USHORT, ULONG*);
rem_port* get_server_port(ULONG, XPM, ULONG, ULONG, ULONG);
ULONG global_pages_per_slot;
ULONG global_slots_per_map;
ULONG xnet_next_free_map_num;
};
static GlobalPtr<XnetClientEndPoint> xnet_client;
static GlobalPtr<XnetServerEndPoint> xnet_server;
} // namespace Remote
inline void make_obj_name(char* buffer, size_t size, const char* format)
inline void XnetEndPoint::make_obj_name(char* buffer, size_t size, const char* format)
{
fb_assert(strcmp(xnet_endpoint, "") != 0);
fb_utils::snprintf(buffer, size, format, xnet_endpoint);
}
inline void make_map_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2)
inline void XnetEndPoint::make_map_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2)
{
fb_assert(strcmp(xnet_endpoint, "") != 0);
fb_utils::snprintf(buffer, size, format, xnet_endpoint, arg1, arg2);
}
inline void make_event_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2, ULONG arg3)
inline void XnetEndPoint::make_event_name(char* buffer, size_t size, const char* format, ULONG arg1, ULONG arg2, ULONG arg3)
{
fb_assert(strcmp(xnet_endpoint, "") != 0);
@ -376,14 +423,19 @@ rem_port* XNET_connect(PACKET* packet,
if (packet)
{
return connect_client(packet, config);
return xnet_client->connect_client(packet, config);
}
return connect_server(flag);
return xnet_server->connect_server(flag);
}
rem_port* XNET_reconnect(ULONG client_pid)
{
return xnet_server->reconnect(client_pid);
}
rem_port* XnetServerEndPoint::reconnect(ULONG client_pid)
{
/**************************************
*
@ -450,7 +502,7 @@ rem_port* XNET_reconnect(ULONG client_pid)
}
static bool connect_init()
bool XnetClientEndPoint::connect_init()
{
/**************************************
*
@ -520,7 +572,7 @@ static bool connect_init()
}
static void connect_fini()
void XnetEndPoint::connect_fini()
{
/**************************************
*
@ -699,7 +751,7 @@ static rem_port* aux_connect(rem_port* port, PACKET* /*packet*/)
parent_xcc = port->port_xcc;
xps = (XPS) parent_xcc->xcc_mapped_addr;
xcc = FB_NEW struct xcc;
xcc = FB_NEW struct xcc(parent_xcc->xcc_endpoint);
xpm = xcc->xcc_xpm = parent_xcc->xcc_xpm;
xcc->xcc_map_num = parent_xcc->xcc_map_num;
@ -712,7 +764,9 @@ static rem_port* aux_connect(rem_port* port, PACKET* /*packet*/)
xcc->xcc_mapped_addr = parent_xcc->xcc_mapped_addr;
xcc->xcc_xpm->xpm_count++;
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_FILLED,
XnetEndPoint* endPoint = xcc->xcc_endpoint;
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_FILLED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_send_channel_filled =
OpenEvent(EVENT_ALL_ACCESS, FALSE, name_buffer);
@ -720,7 +774,7 @@ static rem_port* aux_connect(rem_port* port, PACKET* /*packet*/)
system_call_failed::raise(ERR_STR("OpenEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_EMPTED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_EMPTED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_send_channel_empted =
OpenEvent(EVENT_ALL_ACCESS, FALSE, name_buffer);
@ -728,7 +782,7 @@ static rem_port* aux_connect(rem_port* port, PACKET* /*packet*/)
system_call_failed::raise(ERR_STR("OpenEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_FILLED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_FILLED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_recv_channel_filled =
OpenEvent(EVENT_ALL_ACCESS, FALSE, name_buffer);
@ -736,7 +790,7 @@ static rem_port* aux_connect(rem_port* port, PACKET* /*packet*/)
system_call_failed::raise(ERR_STR("OpenEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_EMPTED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_EMPTED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_recv_channel_empted =
OpenEvent(EVENT_ALL_ACCESS, FALSE, name_buffer);
@ -820,7 +874,7 @@ static rem_port* aux_request(rem_port* port, PACKET* packet)
XCC parent_xcc = port->port_xcc;
XPS xps = (XPS) parent_xcc->xcc_mapped_addr;
xcc = FB_NEW struct xcc;
xcc = FB_NEW struct xcc(parent_xcc->xcc_endpoint);
XPM xpm = xcc->xcc_xpm = parent_xcc->xcc_xpm;
xcc->xcc_map_num = parent_xcc->xcc_map_num;
@ -833,7 +887,9 @@ static rem_port* aux_request(rem_port* port, PACKET* packet)
xcc->xcc_mapped_addr = parent_xcc->xcc_mapped_addr;
xcc->xcc_xpm->xpm_count++;
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_FILLED,
XnetEndPoint* endPoint = xcc->xcc_endpoint;
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_FILLED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_recv_channel_filled =
CreateEvent(ISC_get_security_desc(), FALSE, FALSE, name_buffer);
@ -843,7 +899,7 @@ static rem_port* aux_request(rem_port* port, PACKET* packet)
system_call_failed::raise(ERR_STR("CreateEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_EMPTED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_C2S_EVNT_CHAN_EMPTED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_recv_channel_empted =
CreateEvent(ISC_get_security_desc(), FALSE, TRUE, name_buffer);
@ -853,7 +909,7 @@ static rem_port* aux_request(rem_port* port, PACKET* packet)
system_call_failed::raise(ERR_STR("CreateEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_FILLED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_FILLED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_send_channel_filled =
CreateEvent(ISC_get_security_desc(), FALSE, FALSE, name_buffer);
@ -863,7 +919,7 @@ static rem_port* aux_request(rem_port* port, PACKET* packet)
system_call_failed::raise(ERR_STR("CreateEvent"));
}
make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_EMPTED,
endPoint->make_event_name(name_buffer, sizeof(name_buffer), XNET_E_S2C_EVNT_CHAN_EMPTED,
xcc->xcc_map_num, xcc->xcc_slot, xpm->xpm_timestamp);
xcc->xcc_event_send_channel_empted =
CreateEvent(ISC_get_security_desc(), FALSE, TRUE, name_buffer);
@ -962,14 +1018,14 @@ static void cleanup_comm(XCC xcc)
XPM xpm = xcc->xcc_xpm;
if (xpm && !(xcc->xcc_flags & XCCF_ASYNC)) {
cleanup_mapping(xpm);
xcc->xcc_endpoint->cleanup_mapping(xpm);
}
delete xcc;
}
static void cleanup_mapping(XPM xpm)
void XnetEndPoint::cleanup_mapping(XPM xpm)
{
MutexLockGuard guard(xnet_mutex, FB_FUNCTION);
@ -1029,7 +1085,7 @@ static void raise_lostconn_or_syserror(const char* msg)
}
static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>* config)
rem_port* XnetClientEndPoint::connect_client(PACKET* packet, const RefPtr<Config>* config)
{
/**************************************
*
@ -1083,21 +1139,17 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
return NULL;
}
}
}
// setup status with net read error in case of wait timeout
Arg::StatusVector temp;
temp << Arg::Gds(isc_net_read_err);
static const int timeout = conf->getConnectionTimeout() * 1000;
// waiting for XNET connect lock to release
DWORD err = WaitForSingleObject(xnet_connect_mutex, timeout);
{ // xnet_mutex scope
MutexLockGuard guard(xnet_mutex, FB_FUNCTION);
// setup status with net read error in case of wait timeout
Arg::StatusVector temp;
temp << Arg::Gds(isc_net_read_err);
static const int timeout = conf->getConnectionTimeout() * 1000;
// waiting for XNET connect lock to release
DWORD err = WaitForSingleObject(xnet_connect_mutex, timeout);
if (err != WAIT_OBJECT_0)
{
connect_fini();
@ -1115,15 +1167,10 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
((XNET_RESPONSE*) xnet_connect_map)->proc_id = current_process_id;
SetEvent(xnet_connect_event);
}
// waiting for server response
err = WaitForSingleObject(xnet_response_event, timeout);
{ // xnet_mutex scope
MutexLockGuard guard(xnet_mutex, FB_FUNCTION);
// waiting for server response
err = WaitForSingleObject(xnet_response_event, timeout);
if (err != WAIT_OBJECT_0)
{
ReleaseMutex(xnet_connect_mutex);
@ -1136,7 +1183,7 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
memcpy(&response, xnet_connect_map, XNET_CONNECT_RESPONZE_SIZE);
ReleaseMutex(xnet_connect_mutex);
connect_fini();
}
} // xnet_mutex scope
if (response.map_num == XNET_INVALID_MAP_NUM)
{
@ -1147,8 +1194,6 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
temp.raise();
}
global_pages_per_slot = response.pages_per_slot;
global_slots_per_map = response.slots_per_map;
const ULONG map_num = response.map_num;
const ULONG slot_num = response.slot_num;
const ULONG timestamp = response.timestamp;
@ -1190,7 +1235,7 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
}
mapped_address = MapViewOfFile(file_handle, FILE_MAP_WRITE, 0L, 0L,
XPS_MAPPED_SIZE(global_slots_per_map, global_pages_per_slot));
XPS_MAPPED_SIZE(response.slots_per_map, response.pages_per_slot));
if (!mapped_address) {
system_error::raise(ERR_STR("MapViewOfFile"));
}
@ -1211,11 +1256,11 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
} // xnet_mutex scope
// there's no thread structure, so make one
xcc = FB_NEW struct xcc;
xcc = FB_NEW struct xcc(this);
xcc->xcc_map_handle = xpm->xpm_handle;
xcc->xcc_mapped_addr =
(UCHAR*) xpm->xpm_address + XPS_SLOT_OFFSET(global_pages_per_slot, slot_num);
(UCHAR*)xpm->xpm_address + XPS_SLOT_OFFSET(response.pages_per_slot, slot_num);
xcc->xcc_map_num = map_num;
xcc->xcc_slot = slot_num;
xcc->xcc_xpm = xpm;
@ -1277,7 +1322,7 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
// differ from the server side
const ULONG avail =
(ULONG) (XPS_USEFUL_SPACE(global_pages_per_slot) - (XNET_EVENT_SPACE * 2)) / 2;
(ULONG)(XPS_USEFUL_SPACE(response.pages_per_slot) - (XNET_EVENT_SPACE * 2)) / 2;
UCHAR* start_ptr = (UCHAR*) xps + (sizeof(struct xps) + (XNET_EVENT_SPACE * 2));
// send and receive channels
@ -1318,7 +1363,7 @@ static rem_port* connect_client(PACKET* packet, const Firebird::RefPtr<Config>*
}
static rem_port* connect_server(USHORT flag)
rem_port* XnetServerEndPoint::connect_server(USHORT flag)
{
/**************************************
*
@ -1507,7 +1552,7 @@ static int cleanup_ports(const int, const int, void* /*arg*/)
**************************************/
xnet_shutdown = true;
SetEvent(xnet_connect_event);
SetEvent(xnet_server->xnet_connect_event);
xnet_ports->closePorts();
return 0;
@ -1594,7 +1639,7 @@ static int send_partial( rem_port* port, PACKET* packet)
return xdr_protocol(&port->port_send, packet);
}
static void server_shutdown(rem_port* port)
void XnetClientEndPoint::server_shutdown(rem_port* port)
{
/**************************************
*
@ -1851,7 +1896,8 @@ static bool_t xnet_putbytes(XDR* xdrs, const SCHAR* buff, u_int count)
// Another side is dead or something bad has happened
if (!(xps->xps_flags & XPS_DISCONNECTED) && !portServer)
{
server_shutdown(port);
XnetClientEndPoint* endPoint = static_cast<XnetClientEndPoint*> (xcc->xcc_endpoint);
endPoint->server_shutdown(port);
}
xnet_error(port, isc_lost_db_connection, 0);
@ -1959,7 +2005,8 @@ static bool_t xnet_read(XDR* xdrs)
// Another side is dead or something bad has happened
if (!(xps->xps_flags & XPS_DISCONNECTED) && !portServer)
{
server_shutdown(port);
XnetClientEndPoint* endPoint = static_cast<XnetClientEndPoint*> (xcc->xcc_endpoint);
endPoint->server_shutdown(port);
}
xnet_error(port, isc_lost_db_connection, 0);
@ -2007,7 +2054,7 @@ static bool_t xnet_write(XDR* xdrs)
}
void release_all()
void XnetEndPoint::shutdown()
{
/**************************************
*
@ -2023,10 +2070,13 @@ void release_all()
if (!xnet_initialized)
return;
connect_fini();
MutexLockGuard guard(xnet_mutex, FB_FUNCTION);
if (!xnet_initialized)
return;
connect_fini();
// release all map stuff left not released by broken ports
XPM xpm, nextxpm;
@ -2047,7 +2097,7 @@ void release_all()
/********************** ONLY SERVER CODE FROM HERE *********************/
/***********************************************************************/
static void make_map(ULONG map_number, ULONG timestamp, FILE_ID* map_handle, CADDR_T* map_address)
void XnetServerEndPoint::make_map(ULONG map_number, ULONG timestamp, FILE_ID* map_handle, CADDR_T* map_address)
{
/**************************************
*
@ -2089,7 +2139,7 @@ static void make_map(ULONG map_number, ULONG timestamp, FILE_ID* map_handle, CAD
}
static XPM make_xpm(ULONG map_number, ULONG timestamp)
XPM XnetServerEndPoint::make_xpm(ULONG map_number, ULONG timestamp)
{
/**************************************
*
@ -2130,7 +2180,7 @@ static XPM make_xpm(ULONG map_number, ULONG timestamp)
}
static bool server_init(USHORT flag)
bool XnetServerEndPoint::server_init(USHORT flag)
{
/**************************************
*
@ -2235,7 +2285,7 @@ static bool server_init(USHORT flag)
}
static XPM get_free_slot(ULONG* map_num, ULONG* slot_num, ULONG* timestamp)
XPM XnetServerEndPoint::get_free_slot(ULONG* map_num, ULONG* slot_num, ULONG* timestamp)
{
/**************************************
*
@ -2299,7 +2349,7 @@ static XPM get_free_slot(ULONG* map_num, ULONG* slot_num, ULONG* timestamp)
}
static bool fork(ULONG client_pid, USHORT flag, ULONG* forked_pid)
bool XnetServerEndPoint::fork(ULONG client_pid, USHORT flag, ULONG* forked_pid)
{
/**************************************
*
@ -2351,7 +2401,7 @@ static bool fork(ULONG client_pid, USHORT flag, ULONG* forked_pid)
}
static rem_port* get_server_port(ULONG client_pid,
rem_port* XnetServerEndPoint::get_server_port(ULONG client_pid,
XPM xpm,
ULONG map_num,
ULONG slot_num,
@ -2372,7 +2422,7 @@ static rem_port* get_server_port(ULONG client_pid,
// allocate a communications control structure and fill it in
XCC xcc = FB_NEW struct xcc;
XCC xcc = FB_NEW struct xcc(this);
try {

View File

@ -96,16 +96,23 @@ typedef struct xch
ULONG xch_dummy2; // with 32-bit builds
} *XCH;
namespace Remote {
class XnetEndPoint;
};
// Thread connection control block
typedef struct xcc
{
xcc()
xcc(Remote::XnetEndPoint* endPoint)
{
memset(this, 0, sizeof(*this));
xcc_endpoint = endPoint;
}
struct xcc *xcc_next; // pointer to next thread
Remote::XnetEndPoint* xcc_endpoint; // XnetEndPoint
XPM xcc_xpm; // pointer back to xpm
ULONG xcc_map_num; // this thread's mapped file number
ULONG xcc_slot; // this thread's slot number