/* 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; }
/* 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 }
/* 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; }
/* 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; }
/* 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); }
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"); } }
/* 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 }
/* 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"); } }
static void callback_handler(ffi_cif *cif, void *cb_result, void **cb_args, void *cb_data) { CallbackInvokeData cid; MVMint32 num_roots, i; MVMRegister res; MVMRegister *args; MVMNativeCallback *data = (MVMNativeCallback *)cb_data; void **values = MVM_malloc(sizeof(void *) * (data->cs->arg_count ? data->cs->arg_count : 1)); unsigned int interval_id; /* Locate the MoarVM thread this callback is being run on. */ MVMThreadContext *tc = MVM_nativecall_find_thread_context(data->instance); /* Unblock GC if needed, so this thread can do work. */ MVMint32 was_blocked = MVM_gc_is_thread_blocked(tc); if (was_blocked) MVM_gc_mark_thread_unblocked(tc); interval_id = MVM_telemetry_interval_start(tc, "nativecall callback handler"); /* Build a callsite and arguments buffer. */ args = MVM_malloc(data->num_types * sizeof(MVMRegister)); num_roots = 0; for (i = 1; i < data->num_types; i++) { MVMObject *type = data->types[i]; MVMint16 typeinfo = data->typeinfos[i]; switch (typeinfo & MVM_NATIVECALL_ARG_TYPE_MASK) { case MVM_NATIVECALL_ARG_CHAR: args[i - 1].i64 = *(signed char *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_SHORT: args[i - 1].i64 = *(signed short *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_INT: args[i - 1].i64 = *(signed int *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_LONG: args[i - 1].i64 = *(signed long *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_LONGLONG: args[i - 1].i64 = *(signed long long *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_FLOAT: args[i - 1].n64 = *(float *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_DOUBLE: args[i - 1].n64 = *(double *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_ASCIISTR: case MVM_NATIVECALL_ARG_UTF8STR: case MVM_NATIVECALL_ARG_UTF16STR: args[i - 1].o = MVM_nativecall_make_str(tc, type, typeinfo, *(char **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CSTRUCT: args[i - 1].o = MVM_nativecall_make_cstruct(tc, type, *(void **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CPPSTRUCT: args[i - 1].o = MVM_nativecall_make_cppstruct(tc, type, *(void **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CPOINTER: args[i - 1].o = MVM_nativecall_make_cpointer(tc, type, *(void **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CARRAY: args[i - 1].o = MVM_nativecall_make_carray(tc, type, *(void **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CUNION: args[i - 1].o = MVM_nativecall_make_cunion(tc, type, *(void **)cb_args[i - 1]); MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; break; case MVM_NATIVECALL_ARG_CALLBACK: /* TODO: A callback -return- value means that we have a C method * that needs to be wrapped similarly to a is native(...) Perl 6 * sub. */ /* XXX do something with the function pointer: *(void **)cb_args[i - 1] */ args[i - 1].o = type; MVM_gc_root_temp_push(tc, (MVMCollectable **)&(args[i - 1].o)); num_roots++; case MVM_NATIVECALL_ARG_UCHAR: args[i - 1].i64 = *(unsigned char *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_USHORT: args[i - 1].i64 = *(unsigned short *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_UINT: args[i - 1].i64 = *(unsigned int *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_ULONG: args[i - 1].i64 = *(unsigned long *)cb_args[i - 1]; break; case MVM_NATIVECALL_ARG_ULONGLONG: args[i - 1].i64 = *(unsigned long long *)cb_args[i - 1]; break; default: MVM_telemetry_interval_stop(tc, interval_id, "nativecall callback handler failed"); MVM_exception_throw_adhoc(tc, "Internal error: unhandled libffi callback argument type"); } } /* Call into a nested interpreter (since we already are in one). Need to * save a bunch of state around each side of this. */ cid.invokee = data->target; cid.args = args; cid.cs = data->cs; { MVMuint8 **backup_interp_cur_op = tc->interp_cur_op; MVMuint8 **backup_interp_bytecode_start = tc->interp_bytecode_start; MVMRegister **backup_interp_reg_base = tc->interp_reg_base; MVMCompUnit **backup_interp_cu = tc->interp_cu; MVMFrame *backup_cur_frame = MVM_frame_force_to_heap(tc, tc->cur_frame); MVMFrame *backup_thread_entry_frame = tc->thread_entry_frame; void **backup_jit_return_address = tc->jit_return_address; tc->jit_return_address = NULL; MVMROOT2(tc, backup_cur_frame, backup_thread_entry_frame, { MVMuint32 backup_mark = MVM_gc_root_temp_mark(tc); jmp_buf backup_interp_jump; memcpy(backup_interp_jump, tc->interp_jump, sizeof(jmp_buf)); tc->cur_frame->return_value = &res; tc->cur_frame->return_type = MVM_RETURN_OBJ; MVM_interp_run(tc, callback_invoke, &cid); tc->interp_cur_op = backup_interp_cur_op; tc->interp_bytecode_start = backup_interp_bytecode_start; tc->interp_reg_base = backup_interp_reg_base; tc->interp_cu = backup_interp_cu; tc->cur_frame = backup_cur_frame; tc->current_frame_nr = backup_cur_frame->sequence_nr; tc->thread_entry_frame = backup_thread_entry_frame; tc->jit_return_address = backup_jit_return_address; memcpy(tc->interp_jump, backup_interp_jump, sizeof(jmp_buf)); MVM_gc_root_temp_mark_reset(tc, backup_mark); }); }