Move implementation for threads to Rust
This binds to the appropriate pthreads_* and Windows specific functions and calls them from Rust. This allows for removal of the C++ support code for threads. Fixes #10162
This commit is contained in:
parent
92065ceb63
commit
47e0bd403a
1
mk/rt.mk
1
mk/rt.mk
@ -84,7 +84,6 @@ endif
|
||||
|
||||
RUNTIME_CXXS_$(1)_$(2) := \
|
||||
rt/sync/lock_and_signal.cpp \
|
||||
rt/sync/rust_thread.cpp \
|
||||
rt/rust_builtin.cpp \
|
||||
rt/rust_upcall.cpp \
|
||||
rt/miniz.cpp \
|
||||
|
@ -224,7 +224,10 @@ pub mod types {
|
||||
pub mod common {
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c95::{c_void};
|
||||
use libc::types::os::arch::c95::{c_char, size_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_ulong, size_t};
|
||||
|
||||
pub type pthread_t = c_ulong;
|
||||
|
||||
pub struct glob_t {
|
||||
gl_pathc: size_t,
|
||||
gl_pathv: **c_char,
|
||||
@ -294,7 +297,7 @@ pub mod types {
|
||||
}
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub mod posix01 {
|
||||
use libc::types::os::arch::c95::{c_short, c_long, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_short, c_long, time_t};
|
||||
use libc::types::os::arch::posix88::{dev_t, gid_t, ino_t};
|
||||
use libc::types::os::arch::posix88::{mode_t, off_t};
|
||||
use libc::types::os::arch::posix88::{uid_t};
|
||||
@ -325,10 +328,14 @@ pub mod types {
|
||||
__unused4: c_long,
|
||||
__unused5: c_long,
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__size: [c_char, ..36]
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod posix01 {
|
||||
use libc::types::os::arch::c95::{c_uchar, c_uint, c_ulong, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_uchar, c_uint, c_ulong, time_t};
|
||||
use libc::types::os::arch::c99::{c_longlong, c_ulonglong};
|
||||
use libc::types::os::arch::posix88::{uid_t, gid_t, ino_t};
|
||||
|
||||
@ -357,10 +364,14 @@ pub mod types {
|
||||
st_ctime_nsec: c_ulong,
|
||||
st_ino: c_ulonglong
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__size: [c_char, ..36]
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "mips")]
|
||||
pub mod posix01 {
|
||||
use libc::types::os::arch::c95::{c_long, c_ulong, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_long, c_ulong, time_t};
|
||||
use libc::types::os::arch::posix88::{gid_t, ino_t};
|
||||
use libc::types::os::arch::posix88::{mode_t, off_t};
|
||||
use libc::types::os::arch::posix88::{uid_t};
|
||||
@ -391,6 +402,10 @@ pub mod types {
|
||||
st_blocks: blkcnt_t,
|
||||
st_pad5: [c_long, ..14],
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__size: [c_char, ..36]
|
||||
}
|
||||
}
|
||||
pub mod posix08 {}
|
||||
pub mod bsd44 {}
|
||||
@ -435,7 +450,7 @@ pub mod types {
|
||||
pub type ssize_t = i64;
|
||||
}
|
||||
pub mod posix01 {
|
||||
use libc::types::os::arch::c95::{c_int, c_long, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_int, c_long, time_t};
|
||||
use libc::types::os::arch::posix88::{dev_t, gid_t, ino_t};
|
||||
use libc::types::os::arch::posix88::{mode_t, off_t};
|
||||
use libc::types::os::arch::posix88::{uid_t};
|
||||
@ -463,6 +478,10 @@ pub mod types {
|
||||
st_ctime_nsec: c_long,
|
||||
__unused: [c_long, ..3],
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__size: [c_char, ..56]
|
||||
}
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -479,6 +498,10 @@ pub mod types {
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c95::{c_void};
|
||||
use libc::types::os::arch::c95::{c_char, c_int, size_t};
|
||||
use libc::types::os::arch::c99::{uintptr_t};
|
||||
|
||||
pub type pthread_t = uintptr_t;
|
||||
|
||||
pub struct glob_t {
|
||||
gl_pathc: size_t,
|
||||
__unused1: size_t,
|
||||
@ -535,6 +558,7 @@ pub mod types {
|
||||
pub type ssize_t = i64;
|
||||
}
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c95::{c_void};
|
||||
use libc::types::common::c99::{uint8_t, uint32_t, int32_t};
|
||||
use libc::types::os::arch::c95::{c_long, time_t};
|
||||
use libc::types::os::arch::posix88::{dev_t, gid_t, ino_t};
|
||||
@ -569,6 +593,8 @@ pub mod types {
|
||||
st_birthtime_nsec: c_long,
|
||||
__unused: [uint8_t, ..2],
|
||||
}
|
||||
|
||||
pub type pthread_attr_t = *c_void;
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -945,6 +971,10 @@ pub mod types {
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c95::{c_void};
|
||||
use libc::types::os::arch::c95::{c_char, c_int, size_t};
|
||||
use libc::types::os::arch::c99::{uintptr_t};
|
||||
|
||||
pub type pthread_t = uintptr_t;
|
||||
|
||||
pub struct glob_t {
|
||||
gl_pathc: size_t,
|
||||
__unused1: c_int,
|
||||
@ -1002,7 +1032,7 @@ pub mod types {
|
||||
}
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c99::{int32_t, int64_t, uint32_t};
|
||||
use libc::types::os::arch::c95::{c_long, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_long, time_t};
|
||||
use libc::types::os::arch::posix88::{dev_t, gid_t, ino_t,
|
||||
mode_t, off_t, uid_t};
|
||||
|
||||
@ -1034,6 +1064,11 @@ pub mod types {
|
||||
st_lspare: int32_t,
|
||||
st_qspare: [int64_t, ..2],
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__sig: c_long,
|
||||
__opaque: [c_char, ..36]
|
||||
}
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -1083,7 +1118,7 @@ pub mod types {
|
||||
pub mod posix01 {
|
||||
use libc::types::common::c99::{int32_t, int64_t};
|
||||
use libc::types::common::c99::{uint32_t};
|
||||
use libc::types::os::arch::c95::{c_long, time_t};
|
||||
use libc::types::os::arch::c95::{c_char, c_long, time_t};
|
||||
use libc::types::os::arch::posix88::{dev_t, gid_t, ino_t};
|
||||
use libc::types::os::arch::posix88::{mode_t, off_t, uid_t};
|
||||
|
||||
@ -1115,6 +1150,11 @@ pub mod types {
|
||||
st_lspare: int32_t,
|
||||
st_qspare: [int64_t, ..2],
|
||||
}
|
||||
|
||||
pub struct pthread_attr_t {
|
||||
__sig: c_long,
|
||||
__opaque: [c_char, ..56]
|
||||
}
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -1800,6 +1840,9 @@ pub mod consts {
|
||||
pub static _SC_XOPEN_LEGACY : c_int = 129;
|
||||
pub static _SC_XOPEN_REALTIME : c_int = 130;
|
||||
pub static _SC_XOPEN_REALTIME_THREADS : c_int = 131;
|
||||
|
||||
pub static PTHREAD_CREATE_JOINABLE: c_int = 0;
|
||||
pub static PTHREAD_CREATE_DETACHED: c_int = 1;
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -2207,6 +2250,9 @@ pub mod consts {
|
||||
pub static _SC_XOPEN_UNIX : c_int = 115;
|
||||
pub static _SC_XOPEN_VERSION : c_int = 116;
|
||||
pub static _SC_XOPEN_XCU_VERSION : c_int = 117;
|
||||
|
||||
pub static PTHREAD_CREATE_JOINABLE: c_int = 0;
|
||||
pub static PTHREAD_CREATE_DETACHED: c_int = 1;
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
@ -2560,6 +2606,9 @@ pub mod consts {
|
||||
pub static _SC_XOPEN_UNIX : c_int = 115;
|
||||
pub static _SC_XOPEN_VERSION : c_int = 116;
|
||||
pub static _SC_XOPEN_XCU_VERSION : c_int = 121;
|
||||
|
||||
pub static PTHREAD_CREATE_JOINABLE: c_int = 1;
|
||||
pub static PTHREAD_CREATE_DETACHED: c_int = 2;
|
||||
}
|
||||
pub mod posix08 {
|
||||
}
|
||||
|
@ -8,74 +8,141 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#[allow(non_camel_case_types)];
|
||||
|
||||
use cast;
|
||||
use libc;
|
||||
use ops::Drop;
|
||||
use unstable::raw;
|
||||
use uint;
|
||||
use ptr;
|
||||
|
||||
#[allow(non_camel_case_types)] // runtime type
|
||||
type raw_thread = libc::c_void;
|
||||
#[cfg(windows)]
|
||||
use libc::types::os::arch::extra::{LPSECURITY_ATTRIBUTES, SIZE_T,
|
||||
LPVOID, DWORD, LPDWORD, HANDLE};
|
||||
|
||||
#[cfg(windows)] type rust_thread = HANDLE;
|
||||
#[cfg(unix)] type rust_thread = libc::pthread_t;
|
||||
|
||||
pub struct Thread {
|
||||
priv main: ~fn(),
|
||||
priv raw_thread: *raw_thread,
|
||||
priv native: rust_thread,
|
||||
priv joined: bool
|
||||
}
|
||||
|
||||
static DEFAULT_STACK_SIZE: libc::size_t = 1024*1024;
|
||||
|
||||
#[cfg(windows)] type rust_thread_return = DWORD;
|
||||
#[cfg(unix)] type rust_thread_return = *libc::c_void;
|
||||
|
||||
impl Thread {
|
||||
#[fixed_stack_segment] #[inline(never)]
|
||||
|
||||
pub fn start(main: ~fn()) -> Thread {
|
||||
// This is the starting point of rust os threads. The first thing we do
|
||||
// is make sure that we don't trigger __morestack (also why this has a
|
||||
// no_split_stack annotation), and then we re-build the main function
|
||||
// and invoke it from there.
|
||||
// no_split_stack annotation), and then we extract the main function
|
||||
// and invoke it.
|
||||
#[no_split_stack]
|
||||
extern "C" fn thread_start(code: *(), env: *()) {
|
||||
extern "C" fn thread_start(trampoline: *libc::c_void) -> rust_thread_return {
|
||||
use rt::context;
|
||||
unsafe {
|
||||
context::record_stack_bounds(0, uint::max_value);
|
||||
let f: &fn() = cast::transmute(raw::Closure {
|
||||
code: code,
|
||||
env: env,
|
||||
});
|
||||
f();
|
||||
let f: ~~fn() = cast::transmute(trampoline);
|
||||
(*f)();
|
||||
}
|
||||
unsafe { cast::transmute(0) }
|
||||
}
|
||||
|
||||
let raw_thread = unsafe {
|
||||
let c: raw::Closure = cast::transmute_copy(&main);
|
||||
let raw::Closure { code, env } = c;
|
||||
rust_raw_thread_start(thread_start, code, env)
|
||||
};
|
||||
let native = native_thread_create(thread_start, ~main);
|
||||
Thread {
|
||||
main: main,
|
||||
raw_thread: raw_thread,
|
||||
native: native,
|
||||
joined: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(mut self) {
|
||||
#[fixed_stack_segment]; #[inline(never)];
|
||||
|
||||
assert!(!self.joined);
|
||||
unsafe { rust_raw_thread_join(self.raw_thread); }
|
||||
native_thread_join(self.native);
|
||||
self.joined = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn native_thread_create(thread_start: extern "C" fn(*libc::c_void) -> rust_thread_return,
|
||||
tramp: ~~fn()) -> rust_thread {
|
||||
#[fixed_stack_segment];
|
||||
|
||||
unsafe {
|
||||
let ptr: *mut libc::c_void = cast::transmute(tramp);
|
||||
CreateThread(ptr::mut_null(), DEFAULT_STACK_SIZE, thread_start, ptr, 0, ptr::mut_null())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn native_thread_join(native: rust_thread) {
|
||||
#[fixed_stack_segment];
|
||||
use libc::consts::os::extra::INFINITE;
|
||||
unsafe { WaitForSingleObject(native, INFINITE); }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn native_thread_create(thread_start: extern "C" fn(*libc::c_void) -> rust_thread_return,
|
||||
tramp: ~~fn()) -> rust_thread {
|
||||
#[fixed_stack_segment];
|
||||
|
||||
use unstable::intrinsics;
|
||||
let mut native: libc::pthread_t = unsafe { intrinsics::uninit() };
|
||||
|
||||
unsafe {
|
||||
use libc::consts::os::posix01::PTHREAD_CREATE_JOINABLE;
|
||||
|
||||
let mut attr: libc::pthread_attr_t = intrinsics::uninit();
|
||||
assert!(pthread_attr_init(&mut attr) == 0);
|
||||
assert!(pthread_attr_setstacksize(&mut attr, DEFAULT_STACK_SIZE) == 0);
|
||||
assert!(pthread_attr_setdetachstate(&mut attr, PTHREAD_CREATE_JOINABLE) == 0);
|
||||
|
||||
let ptr: *libc::c_void = cast::transmute(tramp);
|
||||
assert!(pthread_create(&mut native, &attr, thread_start, ptr) == 0);
|
||||
}
|
||||
native
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn native_thread_join(native: rust_thread) {
|
||||
#[fixed_stack_segment];
|
||||
unsafe { assert!(pthread_join(native, ptr::null()) == 0) }
|
||||
}
|
||||
|
||||
impl Drop for Thread {
|
||||
fn drop(&mut self) {
|
||||
#[fixed_stack_segment]; #[inline(never)];
|
||||
|
||||
assert!(self.joined);
|
||||
unsafe { rust_raw_thread_delete(self.raw_thread) }
|
||||
}
|
||||
}
|
||||
|
||||
extern {
|
||||
fn rust_raw_thread_start(f: extern "C" fn(*(), *()),
|
||||
code: *(), env: *()) -> *raw_thread;
|
||||
fn rust_raw_thread_join(thread: *raw_thread);
|
||||
fn rust_raw_thread_delete(thread: *raw_thread);
|
||||
#[cfg(windows, target_arch = "x86")]
|
||||
extern "stdcall" {
|
||||
fn CreateThread(lpThreadAttributes: LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T,
|
||||
lpStartAddress: extern "C" fn(*libc::c_void) -> rust_thread_return,
|
||||
lpParameter: LPVOID, dwCreationFlags: DWORD, lpThreadId: LPDWORD) -> HANDLE;
|
||||
fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
|
||||
}
|
||||
|
||||
#[cfg(windows, target_arch = "x86_64")]
|
||||
extern {
|
||||
fn CreateThread(lpThreadAttributes: LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T,
|
||||
lpStartAddress: extern "C" fn(*libc::c_void) -> rust_thread_return,
|
||||
lpParameter: LPVOID, dwCreationFlags: DWORD, lpThreadId: LPDWORD) -> HANDLE;
|
||||
fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
extern {
|
||||
fn pthread_create(native: *mut libc::pthread_t, attr: *libc::pthread_attr_t,
|
||||
f: extern "C" fn(*libc::c_void) -> rust_thread_return,
|
||||
value: *libc::c_void) -> libc::c_int;
|
||||
fn pthread_join(native: libc::pthread_t, value: **libc::c_void) -> libc::c_int;
|
||||
fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> libc::c_int;
|
||||
fn pthread_attr_setstacksize(attr: *mut libc::pthread_attr_t,
|
||||
stack_size: libc::size_t) -> libc::c_int;
|
||||
fn pthread_attr_setdetachstate(attr: *mut libc::pthread_attr_t,
|
||||
state: libc::c_int) -> libc::c_int;
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
/* Foreign builtins. */
|
||||
|
||||
#include "rust_util.h"
|
||||
#include "sync/rust_thread.h"
|
||||
#include "sync/lock_and_signal.h"
|
||||
#include "vg/valgrind.h"
|
||||
|
||||
@ -385,42 +384,6 @@ rust_signal_little_lock(lock_and_signal *lock) {
|
||||
lock->signal();
|
||||
}
|
||||
|
||||
typedef void(startfn)(void*, void*);
|
||||
|
||||
class raw_thread: public rust_thread {
|
||||
public:
|
||||
startfn *raw_start;
|
||||
void *rust_fn;
|
||||
void *rust_env;
|
||||
|
||||
raw_thread(startfn *raw_start, void *rust_fn, void *rust_env)
|
||||
: raw_start(raw_start), rust_fn(rust_fn), rust_env(rust_env) { }
|
||||
|
||||
virtual void run() {
|
||||
raw_start(rust_fn, rust_env);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" raw_thread*
|
||||
rust_raw_thread_start(startfn *raw_start, void *rust_start, void *rust_env) {
|
||||
assert(raw_start && rust_start);
|
||||
raw_thread *thread = new raw_thread(raw_start, rust_start, rust_env);
|
||||
thread->start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
rust_raw_thread_join(raw_thread *thread) {
|
||||
assert(thread);
|
||||
thread->join();
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
rust_raw_thread_delete(raw_thread *thread) {
|
||||
assert(thread);
|
||||
delete thread;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
@ -42,9 +42,6 @@ rust_signal_little_lock
|
||||
rust_wait_little_lock
|
||||
tdefl_compress_mem_to_heap
|
||||
tinfl_decompress_mem_to_heap
|
||||
rust_raw_thread_start
|
||||
rust_raw_thread_join
|
||||
rust_raw_thread_delete
|
||||
swap_registers
|
||||
rust_readdir
|
||||
rust_opendir
|
||||
|
@ -1,65 +0,0 @@
|
||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
#include "rust_thread.h"
|
||||
#include <limits.h>
|
||||
|
||||
const size_t default_stack_sz = 1024*1024;
|
||||
|
||||
rust_thread::rust_thread() : thread(0) {
|
||||
}
|
||||
|
||||
rust_thread::~rust_thread() {
|
||||
}
|
||||
|
||||
#if defined(__WIN32__)
|
||||
static DWORD WINAPI
|
||||
#elif defined(__GNUC__)
|
||||
static void *
|
||||
#else
|
||||
#error "Platform not supported"
|
||||
#endif
|
||||
rust_thread_start(void *ptr) {
|
||||
rust_thread *thread = (rust_thread *) ptr;
|
||||
thread->run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
rust_thread::start() {
|
||||
#if defined(__WIN32__)
|
||||
thread = CreateThread(NULL, default_stack_sz, rust_thread_start, this, 0, NULL);
|
||||
#else
|
||||
// PTHREAD_STACK_MIN of some system is larger than default size
|
||||
// so we check stack_sz to prevent assertion failure.
|
||||
size_t stack_sz = default_stack_sz;
|
||||
if (stack_sz < PTHREAD_STACK_MIN) {
|
||||
stack_sz = PTHREAD_STACK_MIN;
|
||||
}
|
||||
pthread_attr_t attr;
|
||||
CHECKED(pthread_attr_init(&attr));
|
||||
CHECKED(pthread_attr_setstacksize(&attr, stack_sz));
|
||||
CHECKED(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE));
|
||||
CHECKED(pthread_create(&thread, &attr, rust_thread_start, (void *) this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
rust_thread::join() {
|
||||
#if defined(__WIN32__)
|
||||
if (thread)
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
#else
|
||||
if (thread)
|
||||
CHECKED(pthread_join(thread, NULL));
|
||||
#endif
|
||||
thread = 0;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#ifndef RUST_THREAD_H
|
||||
#define RUST_THREAD_H
|
||||
|
||||
#include "rust_globals.h"
|
||||
|
||||
/**
|
||||
* Thread utility class. Derive and implement your own run() method.
|
||||
*/
|
||||
class rust_thread {
|
||||
private:
|
||||
#if defined(__WIN32__)
|
||||
HANDLE thread;
|
||||
#else
|
||||
pthread_t thread;
|
||||
#endif
|
||||
public:
|
||||
|
||||
rust_thread();
|
||||
virtual ~rust_thread();
|
||||
|
||||
void start();
|
||||
|
||||
virtual void run() = 0;
|
||||
|
||||
void join();
|
||||
};
|
||||
|
||||
#endif /* RUST_THREAD_H */
|
Loading…
Reference in New Issue
Block a user