/* Locks a file. */ static MVMint64 lock(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 flag) { MVMIOFileData *data = (MVMIOFileData *)h->body.data; #ifdef _WIN32 const DWORD len = 0xffffffff; const HANDLE hf = (HANDLE)_get_osfhandle(data->fd); OVERLAPPED offset; if (hf == INVALID_HANDLE_VALUE) { MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: bad file descriptor"); } flag = ((flag & MVM_FILE_FLOCK_NONBLOCK) ? LOCKFILE_FAIL_IMMEDIATELY : 0) + ((flag & MVM_FILE_FLOCK_TYPEMASK) == MVM_FILE_FLOCK_SHARED ? 0 : LOCKFILE_EXCLUSIVE_LOCK); memset (&offset, 0, sizeof(offset)); MVM_gc_mark_thread_blocked(tc); if (LockFileEx(hf, flag, 0, len, len, &offset)) { MVM_gc_mark_thread_unblocked(tc); return 1; } MVM_gc_mark_thread_unblocked(tc); MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: %d", GetLastError()); return 0; #else struct flock l; ssize_t r; int fc; const int fd = data->fd; l.l_whence = SEEK_SET; l.l_start = 0; l.l_len = 0; if ((flag & MVM_FILE_FLOCK_TYPEMASK) == MVM_FILE_FLOCK_SHARED) l.l_type = F_RDLCK; else l.l_type = F_WRLCK; fc = (flag & MVM_FILE_FLOCK_NONBLOCK) ? F_SETLK : F_SETLKW; do { MVM_gc_mark_thread_blocked(tc); r = fcntl(fd, fc, &l); MVM_gc_mark_thread_unblocked(tc); } while (r == -1 && errno == EINTR); if (r == -1) { MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: %d", errno); } return 1; #endif }
/* This callback handles starting execution of a thread. */ static void start_thread(void *data) { ThreadStart *ts = (ThreadStart *)data; MVMThreadContext *tc = ts->tc; /* Set the current frame in the thread to be the initial caller; * the ref count for this was incremented in the original thread. */ tc->cur_frame = ts->caller; /* wait for the GC to finish if it's not finished stealing us. */ MVM_gc_mark_thread_unblocked(tc); tc->thread_obj->body.stage = MVM_thread_stage_started; /* Enter the interpreter, to run code. */ MVM_interp_run(tc, &thread_initial_invoke, ts); /* mark as exited, so the GC will know to clear our stuff. */ tc->thread_obj->body.stage = MVM_thread_stage_exited; /* Now we're done, decrement the reference count of the caller. */ MVM_frame_dec_ref(tc, ts->caller); /* Mark ourselves as dying, so that another thread will take care * of GC-ing our objects and cleaning up our thread context. */ MVM_gc_mark_thread_blocked(tc); /* hopefully pop the ts->thread_obj temp */ MVM_gc_root_temp_pop(tc); free(ts); /* Exit the thread, now it's completed. */ MVM_platform_thread_exit(NULL); }
/* Reads the specified number of bytes into a the supplied buffer, returning * the number actually read. */ static MVMint64 read_bytes(MVMThreadContext *tc, MVMOSHandle *h, char **buf_out, MVMint64 bytes) { MVMIOFileData *data = (MVMIOFileData *)h->body.data; char *buf = MVM_malloc(bytes); unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncfile.read_to_buffer"); MVMint32 bytes_read; #ifdef _WIN32 /* Can only perform relatively small reads from a Windows console; * trying to do larger ones gives back ENOMEM, most likely due to * limitations of the Windows console subsystem. */ if (bytes > 16387 && _isatty(data->fd)) bytes = 16387; #endif flush_output_buffer(tc, data); MVM_gc_mark_thread_blocked(tc); if ((bytes_read = read(data->fd, buf, bytes)) == -1) { int save_errno = errno; MVM_free(buf); MVM_gc_mark_thread_unblocked(tc); MVM_exception_throw_adhoc(tc, "Reading from filehandle failed: %s", strerror(save_errno)); } *buf_out = buf; MVM_gc_mark_thread_unblocked(tc); MVM_telemetry_interval_annotate(bytes_read, interval_id, "read this many bytes"); MVM_telemetry_interval_stop(tc, interval_id, "syncfile.read_to_buffer"); data->byte_position += bytes_read; if (bytes_read == 0 && bytes != 0) data->eof_reported = 1; return bytes_read; }
static MVMObject * socket_accept(MVMThreadContext *tc, MVMOSHandle *h) { MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data; Socket s; unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncsocket accept"); do { MVM_gc_mark_thread_blocked(tc); s = accept(data->handle, NULL, NULL); MVM_gc_mark_thread_unblocked(tc); } while(s == -1 && errno == EINTR); if (MVM_IS_SOCKET_ERROR(s)) { MVM_telemetry_interval_stop(tc, interval_id, "syncsocket accept failed"); throw_error(tc, s, "accept socket connection"); } else { MVMOSHandle * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO); MVMIOSyncSocketData * const data = MVM_calloc(1, sizeof(MVMIOSyncSocketData)); data->handle = s; result->body.ops = &op_table; result->body.data = data; MVM_telemetry_interval_stop(tc, interval_id, "syncsocket accept succeeded"); return (MVMObject *)result; } }
/* Establishes a connection. */ static void socket_connect(MVMThreadContext *tc, MVMOSHandle *h, MVMString *host, MVMint64 port) { MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data; unsigned int interval_id; interval_id = MVM_telemetry_interval_start(tc, "syncsocket connect"); if (!data->handle) { struct sockaddr *dest = MVM_io_resolve_host_name(tc, host, port); int r; Socket s = socket(dest->sa_family , SOCK_STREAM , 0); if (MVM_IS_SOCKET_ERROR(s)) { MVM_free(dest); MVM_telemetry_interval_stop(tc, interval_id, "syncsocket connect"); throw_error(tc, s, "create socket"); } do { MVM_gc_mark_thread_blocked(tc); r = connect(s, dest, (socklen_t)get_struct_size_for_family(dest->sa_family)); MVM_gc_mark_thread_unblocked(tc); } while(r == -1 && errno == EINTR); MVM_free(dest); if (MVM_IS_SOCKET_ERROR(r)) { MVM_telemetry_interval_stop(tc, interval_id, "syncsocket connect"); throw_error(tc, s, "connect socket"); } data->handle = s; } else { MVM_telemetry_interval_stop(tc, interval_id, "syncsocket didn't connect"); MVM_exception_throw_adhoc(tc, "Socket is already bound or connected"); } }
/* Writes the specified bytes to the stream. */ MVMint64 socket_write_bytes(MVMThreadContext *tc, MVMOSHandle *h, char *buf, MVMint64 bytes) { MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data; MVMint64 sent = 0; unsigned int interval_id; interval_id = MVM_telemetry_interval_start(tc, "syncsocket.write_bytes"); MVM_gc_mark_thread_blocked(tc); while (bytes > 0) { int r; do { r = send(data->handle, buf, (int)bytes, 0); } while(r == -1 && errno == EINTR); if (MVM_IS_SOCKET_ERROR(r)) { MVM_gc_mark_thread_unblocked(tc); MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.write_bytes"); throw_error(tc, r, "send data to socket"); } sent += r; buf += r; bytes -= r; } MVM_gc_mark_thread_unblocked(tc); MVM_telemetry_interval_annotate(bytes, interval_id, "written this many bytes"); MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.write_bytes"); return bytes; }
/* This function may return any type of sockaddr e.g. sockaddr_un, sockaddr_in or sockaddr_in6 * It shouldn't be a problem with general code as long as the port number is kept below the int16 limit: 65536 * After this it defines the family which may spawn non internet sockaddr's * The family can be extracted by (port >> 16) & USHORT_MAX * * Currently supported families: * * AF_UNSPEC = 1 * Unspecified, in most cases should be equal to AF_INET or AF_INET6 * * AF_UNIX = 1 * Unix domain socket, will spawn a sockaddr_un which will use the given host as path * e.g: MVM_io_resolve_host_name(tc, "/run/moarvm.sock", 1 << 16) * will spawn an unix domain socket on /run/moarvm.sock * * AF_INET = 2 * IPv4 socket * * AF_INET6 = 10 * IPv6 socket */ struct sockaddr * MVM_io_resolve_host_name(MVMThreadContext *tc, MVMString *host, MVMint64 port) { char *host_cstr = MVM_string_utf8_encode_C_string(tc, host); struct sockaddr *dest; int error; struct addrinfo *result; char port_cstr[8]; unsigned short family = (port >> 16) & USHRT_MAX; struct addrinfo hints; #ifndef _WIN32 /* AF_UNIX = 1 */ if (family == AF_UNIX) { struct sockaddr_un *result_un = MVM_malloc(sizeof(struct sockaddr_un)); if (strlen(host_cstr) > 107) { MVM_free(result_un); MVM_free(host_cstr); MVM_exception_throw_adhoc(tc, "Socket path can only be maximal 107 characters long"); } result_un->sun_family = AF_UNIX; strcpy(result_un->sun_path, host_cstr); MVM_free(host_cstr); return (struct sockaddr *)result_un; } #endif hints.ai_family = family; hints.ai_socktype = 0; hints.ai_flags = AI_PASSIVE; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; hints.ai_next = NULL; snprintf(port_cstr, 8, "%d", (int)port); MVM_gc_mark_thread_blocked(tc); error = getaddrinfo(host_cstr, port_cstr, &hints, &result); MVM_gc_mark_thread_unblocked(tc); if (error == 0) { size_t size = get_struct_size_for_family(result->ai_addr->sa_family); MVM_free(host_cstr); dest = MVM_malloc(size); memcpy(dest, result->ai_addr, size); } else { char *waste[] = { host_cstr, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Failed to resolve host name '%s' with family %d. Error: '%s'", host_cstr, family, gai_strerror(error)); } freeaddrinfo(result); return dest; }
/* Unlocks a file. */ static void unlock(MVMThreadContext *tc, MVMOSHandle *h) { MVMIOFileData *data = (MVMIOFileData *)h->body.data; #ifdef _WIN32 const DWORD len = 0xffffffff; const HANDLE hf = (HANDLE)_get_osfhandle(data->fd); OVERLAPPED offset; if (hf == INVALID_HANDLE_VALUE) { MVM_exception_throw_adhoc(tc, "Failed to seek in filehandle: bad file descriptor"); } memset (&offset, 0, sizeof(offset)); MVM_gc_mark_thread_blocked(tc); if (UnlockFileEx(hf, 0, len, len, &offset)) { MVM_gc_mark_thread_unblocked(tc); return; } MVM_gc_mark_thread_unblocked(tc); MVM_exception_throw_adhoc(tc, "Failed to unlock filehandle: %d", GetLastError()); #else struct flock l; ssize_t r; const int fd = data->fd; l.l_whence = SEEK_SET; l.l_start = 0; l.l_len = 0; l.l_type = F_UNLCK; do { MVM_gc_mark_thread_blocked(tc); r = fcntl(fd, F_SETLKW, &l); MVM_gc_mark_thread_unblocked(tc); } while (r == -1 && errno == EINTR); if (r == -1) { MVM_exception_throw_adhoc(tc, "Failed to unlock filehandle: %d", errno); } #endif }
/* Read a bunch of bytes into the current decode stream. */ static MVMint32 read_to_buffer(MVMThreadContext *tc, MVMIOFileData *data, MVMint32 bytes) { char *buf = MVM_malloc(bytes); uv_buf_t read_buf = uv_buf_init(buf, bytes); uv_fs_t req; MVMint32 read; MVM_gc_mark_thread_blocked(tc); if ((read = uv_fs_read(tc->loop, &req, data->fd, &read_buf, 1, -1, NULL)) < 0) { MVM_free(buf); MVM_gc_mark_thread_unblocked(tc); MVM_exception_throw_adhoc(tc, "Reading from filehandle failed: %s", uv_strerror(req.result)); } MVM_string_decodestream_add_bytes(tc, data->ds, buf, read); MVM_gc_mark_thread_unblocked(tc); return read; }
/* Performs a write, either because a buffer filled or because we are not * buffering output. */ static void perform_write(MVMThreadContext *tc, MVMIOFileData *data, char *buf, MVMint64 bytes) { MVMint64 bytes_written = 0; MVM_gc_mark_thread_blocked(tc); while (bytes > 0) { int r = write(data->fd, buf, (int)bytes); if (r == -1) { int save_errno = errno; MVM_gc_mark_thread_unblocked(tc); MVM_exception_throw_adhoc(tc, "Failed to write bytes to filehandle: %s", strerror(save_errno)); } bytes_written += r; buf += r; bytes -= r; } MVM_gc_mark_thread_unblocked(tc); data->byte_position += bytes_written; data->known_writable = 1; }
/* Read a packet worth of data into the last packet buffer. */ static void read_one_packet(MVMThreadContext *tc, MVMIOSyncSocketData *data) { unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncsocket.read_one_packet"); int r; data->last_packet = MVM_malloc(PACKET_SIZE); do { MVM_gc_mark_thread_blocked(tc); r = recv(data->handle, data->last_packet, PACKET_SIZE, 0); MVM_gc_mark_thread_unblocked(tc); } while(r == -1 && errno == EINTR); MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.read_one_packet"); if (MVM_IS_SOCKET_ERROR(r) || r == 0) { MVM_free(data->last_packet); data->last_packet = NULL; if (r != 0) throw_error(tc, r, "receive data from socket"); } else { data->last_packet_start = 0; data->last_packet_end = r; } }
static MVMObject * socket_accept(MVMThreadContext *tc, MVMOSHandle *h) { MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data; while (!data->accept_server) { if (tc->loop != data->ss.handle->loop) { MVM_exception_throw_adhoc(tc, "Tried to accept() on a socket from outside its originating thread"); } uv_ref((uv_handle_t *)data->ss.handle); MVM_gc_mark_thread_blocked(tc); uv_run(tc->loop, UV_RUN_DEFAULT); MVM_gc_mark_thread_unblocked(tc); } /* Check the accept worked out. */ if (data->accept_status < 0) { MVM_exception_throw_adhoc(tc, "Failed to listen: unknown error"); } else { uv_tcp_t *client = MVM_malloc(sizeof(uv_tcp_t)); uv_stream_t *server = data->accept_server; int r; uv_tcp_init(tc->loop, client); data->accept_server = NULL; if ((r = uv_accept(server, (uv_stream_t *)client)) == 0) { MVMOSHandle * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO); MVMIOSyncSocketData * const data = MVM_calloc(1, sizeof(MVMIOSyncSocketData)); data->ss.handle = (uv_stream_t *)client; data->ss.encoding = MVM_encoding_type_utf8; MVM_string_decode_stream_sep_default(tc, &(data->ss.sep_spec)); result->body.ops = &op_table; result->body.data = data; return (MVMObject *)result; } else { uv_close((uv_handle_t*)client, NULL); MVM_free(client); MVM_exception_throw_adhoc(tc, "Failed to accept: %s", uv_strerror(r)); } } }
void MVM_thread_join(MVMThreadContext *tc, MVMObject *thread_obj) { if (REPR(thread_obj)->ID == MVM_REPR_ID_MVMThread) { MVMThread *thread = (MVMThread *)thread_obj; int status; MVM_gc_root_temp_push(tc, (MVMCollectable **)&thread); MVM_gc_mark_thread_blocked(tc); if (((MVMThread *)thread_obj)->body.stage < MVM_thread_stage_exited) { status = uv_thread_join(&thread->body.thread); } else { /* the target already ended */ /* used to be APR_SUCCESS, but then we ditched APR */ status = 0; } MVM_gc_mark_thread_unblocked(tc); MVM_gc_root_temp_pop(tc); if (status < 0) MVM_panic(MVM_exitcode_compunit, "Could not join thread: errorcode %d", status); } else { MVM_exception_throw_adhoc(tc, "Thread handle passed to join must have representation MVMThread"); } }
MVMObject * MVM_thread_start(MVMThreadContext *tc, MVMObject *invokee, MVMObject *result_type) { int status; ThreadStart *ts; MVMObject *child_obj; /* Create a thread object to wrap it up in. */ MVM_gc_root_temp_push(tc, (MVMCollectable **)&invokee); child_obj = REPR(result_type)->allocate(tc, STABLE(result_type)); MVM_gc_root_temp_pop(tc); if (REPR(child_obj)->ID == MVM_REPR_ID_MVMThread) { MVMThread *child = (MVMThread *)child_obj; MVMThread * volatile *threads; /* Create a new thread context and set it up. */ MVMThreadContext *child_tc = MVM_tc_create(tc->instance); child->body.tc = child_tc; MVM_ASSIGN_REF(tc, child, child->body.invokee, invokee); child_tc->thread_obj = child; child_tc->thread_id = MVM_incr(&tc->instance->next_user_thread_id); /* Create the thread. Note that we take a reference to the current frame, * since it must survive to be the dynamic scope of where the thread was * started, and there's no promises that the thread won't start before * the code creating the thread returns. The count is decremented when * the thread is done. */ ts = malloc(sizeof(ThreadStart)); ts->tc = child_tc; ts->caller = MVM_frame_inc_ref(tc, tc->cur_frame); ts->thread_obj = child_obj; /* push this to the *child* tc's temp roots. */ MVM_gc_root_temp_push(child_tc, (MVMCollectable **)&ts->thread_obj); /* Signal to the GC we have a childbirth in progress. The GC * will null it for us. */ MVM_gc_mark_thread_blocked(child_tc); MVM_ASSIGN_REF(tc, tc->thread_obj, tc->thread_obj->body.new_child, child); /* push to starting threads list */ threads = &tc->instance->threads; do { MVMThread *curr = *threads; MVM_ASSIGN_REF(tc, child, child->body.next, curr); } while (MVM_casptr(threads, child->body.next, child) != child->body.next); status = uv_thread_create(&child->body.thread, &start_thread, ts); if (status < 0) { MVM_panic(MVM_exitcode_compunit, "Could not spawn thread: errorcode %d", status); } /* need to run the GC to clear our new_child field in case we try * try to launch another thread before the GC runs and before the * thread starts. */ GC_SYNC_POINT(tc); } else { MVM_exception_throw_adhoc(tc, "Thread result type must have representation MVMThread"); } return child_obj; }