/* Initiates a write. */ static void win_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, grpc_slice_buffer *slices, grpc_closure *cb) { grpc_tcp *tcp = (grpc_tcp *)ep; grpc_winsocket *socket = tcp->socket; grpc_winsocket_callback_info *info = &socket->write_info; unsigned i; DWORD bytes_sent; int status; WSABUF local_buffers[16]; WSABUF *allocated = NULL; WSABUF *buffers = local_buffers; size_t len; if (tcp->shutting_down) { GRPC_CLOSURE_SCHED( exec_ctx, cb, GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( "TCP socket is shutting down", &tcp->shutdown_error, 1)); return; } tcp->write_cb = cb; tcp->write_slices = slices; GPR_ASSERT(tcp->write_slices->count <= UINT_MAX); if (tcp->write_slices->count > GPR_ARRAY_SIZE(local_buffers)) { buffers = (WSABUF *)gpr_malloc(sizeof(WSABUF) * tcp->write_slices->count); allocated = buffers; } for (i = 0; i < tcp->write_slices->count; i++) { len = GRPC_SLICE_LENGTH(tcp->write_slices->slices[i]); GPR_ASSERT(len <= ULONG_MAX); buffers[i].len = (ULONG)len; buffers[i].buf = (char *)GRPC_SLICE_START_PTR(tcp->write_slices->slices[i]); } /* First, let's try a synchronous, non-blocking write. */ status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, &bytes_sent, 0, NULL, NULL); info->wsa_error = status == 0 ? 0 : WSAGetLastError(); /* We would kind of expect to get a WSAEWOULDBLOCK here, especially on a busy connection that has its send queue filled up. But if we don't, then we can avoid doing an async write operation at all. */ if (info->wsa_error != WSAEWOULDBLOCK) { grpc_error *error = status == 0 ? GRPC_ERROR_NONE : GRPC_WSA_ERROR(info->wsa_error, "WSASend"); GRPC_CLOSURE_SCHED(exec_ctx, cb, error); if (allocated) gpr_free(allocated); return; } TCP_REF(tcp, "write"); /* If we got a WSAEWOULDBLOCK earlier, then we need to re-do the same operation, this time asynchronously. */ memset(&socket->write_info.overlapped, 0, sizeof(OVERLAPPED)); status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, &bytes_sent, 0, &socket->write_info.overlapped, NULL); if (allocated) gpr_free(allocated); if (status != 0) { int wsa_error = WSAGetLastError(); if (wsa_error != WSA_IO_PENDING) { TCP_UNREF(exec_ctx, tcp, "write"); GRPC_CLOSURE_SCHED(exec_ctx, cb, GRPC_WSA_ERROR(wsa_error, "WSASend")); return; } } /* As all is now setup, we can now ask for the IOCP notification. It may trigger the callback immediately however, but no matter. */ grpc_socket_notify_on_write(exec_ctx, socket, &tcp->on_write); }
/* Tries to issue one async connection, then schedules both an IOCP notification request for the connection, and one timeout alert. */ void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *on_done, grpc_endpoint **endpoint, grpc_pollset_set *interested_parties, const struct sockaddr *addr, size_t addr_len, gpr_timespec deadline) { SOCKET sock = INVALID_SOCKET; BOOL success; int status; struct sockaddr_in6 addr6_v4mapped; struct sockaddr_in6 local_address; async_connect *ac; grpc_winsocket *socket = NULL; LPFN_CONNECTEX ConnectEx; GUID guid = WSAID_CONNECTEX; DWORD ioctl_num_bytes; const char *message = NULL; char *utf8_message; grpc_winsocket_callback_info *info; *endpoint = NULL; /* Use dualstack sockets where available. */ if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { addr = (const struct sockaddr *)&addr6_v4mapped; addr_len = sizeof(addr6_v4mapped); } sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { message = "Unable to create socket: %s"; goto failure; } if (!grpc_tcp_prepare_socket(sock)) { message = "Unable to set socket options: %s"; goto failure; } /* Grab the function pointer for ConnectEx for that specific socket. It may change depending on the interface. */ status = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &ioctl_num_bytes, NULL, NULL); if (status != 0) { message = "Unable to retrieve ConnectEx pointer: %s"; goto failure; } grpc_sockaddr_make_wildcard6(0, &local_address); status = bind(sock, (struct sockaddr *)&local_address, sizeof(local_address)); if (status != 0) { message = "Unable to bind socket: %s"; goto failure; } socket = grpc_winsocket_create(sock, "client"); info = &socket->write_info; success = ConnectEx(sock, addr, (int)addr_len, NULL, 0, NULL, &info->overlapped); /* It wouldn't be unusual to get a success immediately. But we'll still get an IOCP notification, so let's ignore it. */ if (!success) { int error = WSAGetLastError(); if (error != ERROR_IO_PENDING) { message = "ConnectEx failed: %s"; goto failure; } } ac = gpr_malloc(sizeof(async_connect)); ac->on_done = on_done; ac->socket = socket; gpr_mu_init(&ac->mu); ac->refs = 2; ac->addr_name = grpc_sockaddr_to_uri(addr); ac->endpoint = endpoint; grpc_closure_init(&ac->on_connect, on_connect, ac); grpc_timer_init(exec_ctx, &ac->alarm, deadline, on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC)); grpc_socket_notify_on_write(exec_ctx, socket, &ac->on_connect); return; failure: utf8_message = gpr_format_message(WSAGetLastError()); gpr_log(GPR_ERROR, message, utf8_message); gpr_free(utf8_message); if (socket != NULL) { grpc_winsocket_destroy(socket); } else if (sock != INVALID_SOCKET) { closesocket(sock); } grpc_exec_ctx_enqueue(exec_ctx, on_done, 0); }
/* Initiates a write. */ static grpc_endpoint_write_status win_write(grpc_endpoint *ep, gpr_slice *slices, size_t nslices, grpc_endpoint_write_cb cb, void *arg) { grpc_tcp *tcp = (grpc_tcp *) ep; grpc_winsocket *socket = tcp->socket; grpc_winsocket_callback_info *info = &socket->write_info; unsigned i; DWORD bytes_sent; int status; WSABUF local_buffers[16]; WSABUF *allocated = NULL; WSABUF *buffers = local_buffers; GPR_ASSERT(!tcp->outstanding_write); GPR_ASSERT(!tcp->shutting_down); tcp_ref(tcp); tcp->outstanding_write = 1; tcp->write_cb = cb; tcp->write_user_data = arg; gpr_slice_buffer_addn(&tcp->write_slices, slices, nslices); if (tcp->write_slices.count > GPR_ARRAY_SIZE(local_buffers)) { buffers = (WSABUF *) gpr_malloc(sizeof(WSABUF) * tcp->write_slices.count); allocated = buffers; } for (i = 0; i < tcp->write_slices.count; i++) { buffers[i].len = GPR_SLICE_LENGTH(tcp->write_slices.slices[i]); buffers[i].buf = (char *)GPR_SLICE_START_PTR(tcp->write_slices.slices[i]); } /* First, let's try a synchronous, non-blocking write. */ status = WSASend(socket->socket, buffers, tcp->write_slices.count, &bytes_sent, 0, NULL, NULL); info->wsa_error = status == 0 ? 0 : WSAGetLastError(); /* We would kind of expect to get a WSAEWOULDBLOCK here, especially on a busy connection that has its send queue filled up. But if we don't, then we can avoid doing an async write operation at all. */ if (info->wsa_error != WSAEWOULDBLOCK) { grpc_endpoint_write_status ret = GRPC_ENDPOINT_WRITE_ERROR; if (status == 0) { ret = GRPC_ENDPOINT_WRITE_DONE; GPR_ASSERT(bytes_sent == tcp->write_slices.length); } else { char *utf8_message = gpr_format_message(info->wsa_error); gpr_log(GPR_ERROR, "WSASend error: %s", utf8_message); gpr_free(utf8_message); } if (allocated) gpr_free(allocated); gpr_slice_buffer_reset_and_unref(&tcp->write_slices); tcp->outstanding_write = 0; tcp_unref(tcp); return ret; } /* If we got a WSAEWOULDBLOCK earlier, then we need to re-do the same operation, this time asynchronously. */ memset(&socket->write_info.overlapped, 0, sizeof(OVERLAPPED)); status = WSASend(socket->socket, buffers, tcp->write_slices.count, &bytes_sent, 0, &socket->write_info.overlapped, NULL); if (allocated) gpr_free(allocated); /* It is possible the operation completed then. But we'd still get an IOCP notification. So let's ignore it and wait for the IOCP. */ if (status != 0) { int error = WSAGetLastError(); if (error != WSA_IO_PENDING) { char *utf8_message = gpr_format_message(WSAGetLastError()); gpr_log(GPR_ERROR, "WSASend error: %s - this means we're going to leak.", utf8_message); gpr_free(utf8_message); /* I'm pretty sure this is a very bad situation there. Hence the log. What will happen now is that the socket will neither wait for read or write, unless the caller retry, which is unlikely, but I am not sure if that's guaranteed. And there might also be a read pending. This means that the future orphanage of that socket will be in limbo, and we're going to leak it. I have no idea what could cause this specific case however, aside from a parameter error from our call. Normal read errors would actually happen during the overlapped operation, which is the supported way to go for that. */ tcp->outstanding_write = 0; tcp_unref(tcp); /* Per the comment above, I'm going to treat that case as a hard failure for now, and leave the option to catch that and debug. */ __debugbreak(); return GRPC_ENDPOINT_WRITE_ERROR; } } /* As all is now setup, we can now ask for the IOCP notification. It may trigger the callback immediately however, but no matter. */ grpc_socket_notify_on_write(socket, on_write, tcp); return GRPC_ENDPOINT_WRITE_PENDING; }
/* Initiates a write. */ static grpc_endpoint_write_status win_write(grpc_endpoint *ep, gpr_slice *slices, size_t nslices, grpc_endpoint_write_cb cb, void *arg) { grpc_tcp *tcp = (grpc_tcp *) ep; grpc_winsocket *socket = tcp->socket; grpc_winsocket_callback_info *info = &socket->write_info; unsigned i; DWORD bytes_sent; int status; WSABUF local_buffers[16]; WSABUF *allocated = NULL; WSABUF *buffers = local_buffers; GPR_ASSERT(!tcp->socket->write_info.outstanding); if (tcp->shutting_down) { return GRPC_ENDPOINT_WRITE_ERROR; } tcp_ref(tcp); tcp->socket->write_info.outstanding = 1; tcp->write_cb = cb; tcp->write_user_data = arg; gpr_slice_buffer_addn(&tcp->write_slices, slices, nslices); if (tcp->write_slices.count > GPR_ARRAY_SIZE(local_buffers)) { buffers = (WSABUF *) gpr_malloc(sizeof(WSABUF) * tcp->write_slices.count); allocated = buffers; } for (i = 0; i < tcp->write_slices.count; i++) { buffers[i].len = GPR_SLICE_LENGTH(tcp->write_slices.slices[i]); buffers[i].buf = (char *)GPR_SLICE_START_PTR(tcp->write_slices.slices[i]); } /* First, let's try a synchronous, non-blocking write. */ status = WSASend(socket->socket, buffers, tcp->write_slices.count, &bytes_sent, 0, NULL, NULL); info->wsa_error = status == 0 ? 0 : WSAGetLastError(); /* We would kind of expect to get a WSAEWOULDBLOCK here, especially on a busy connection that has its send queue filled up. But if we don't, then we can avoid doing an async write operation at all. */ if (info->wsa_error != WSAEWOULDBLOCK) { grpc_endpoint_write_status ret = GRPC_ENDPOINT_WRITE_ERROR; if (status == 0) { ret = GRPC_ENDPOINT_WRITE_DONE; GPR_ASSERT(bytes_sent == tcp->write_slices.length); } else { char *utf8_message = gpr_format_message(info->wsa_error); gpr_log(GPR_ERROR, "WSASend error: %s", utf8_message); gpr_free(utf8_message); } if (allocated) gpr_free(allocated); gpr_slice_buffer_reset_and_unref(&tcp->write_slices); tcp->socket->write_info.outstanding = 0; tcp_unref(tcp); return ret; } /* If we got a WSAEWOULDBLOCK earlier, then we need to re-do the same operation, this time asynchronously. */ memset(&socket->write_info.overlapped, 0, sizeof(OVERLAPPED)); status = WSASend(socket->socket, buffers, tcp->write_slices.count, &bytes_sent, 0, &socket->write_info.overlapped, NULL); if (allocated) gpr_free(allocated); if (status != 0) { int wsa_error = WSAGetLastError(); if (wsa_error != WSA_IO_PENDING) { gpr_slice_buffer_reset_and_unref(&tcp->write_slices); tcp->socket->write_info.outstanding = 0; tcp_unref(tcp); return GRPC_ENDPOINT_WRITE_ERROR; } } /* As all is now setup, we can now ask for the IOCP notification. It may trigger the callback immediately however, but no matter. */ grpc_socket_notify_on_write(socket, on_write, tcp); return GRPC_ENDPOINT_WRITE_PENDING; }
/* Tries to issue one async connection, then schedules both an IOCP notification request for the connection, and one timeout alert. */ void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *on_done, grpc_endpoint **endpoint, grpc_pollset_set *interested_parties, const grpc_channel_args *channel_args, const grpc_resolved_address *addr, gpr_timespec deadline) { SOCKET sock = INVALID_SOCKET; BOOL success; int status; grpc_resolved_address addr6_v4mapped; grpc_resolved_address local_address; async_connect *ac; grpc_winsocket *socket = NULL; LPFN_CONNECTEX ConnectEx; GUID guid = WSAID_CONNECTEX; DWORD ioctl_num_bytes; grpc_winsocket_callback_info *info; grpc_error *error = GRPC_ERROR_NONE; grpc_resource_quota *resource_quota = grpc_resource_quota_create(NULL); if (channel_args != NULL) { for (size_t i = 0; i < channel_args->num_args; i++) { if (0 == strcmp(channel_args->args[i].key, GRPC_ARG_RESOURCE_QUOTA)) { grpc_resource_quota_internal_unref(exec_ctx, resource_quota); resource_quota = grpc_resource_quota_internal_ref( channel_args->args[i].value.pointer.p); } } } *endpoint = NULL; /* Use dualstack sockets where available. */ if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { addr = &addr6_v4mapped; } sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { error = GRPC_WSA_ERROR(WSAGetLastError(), "WSASocket"); goto failure; } error = grpc_tcp_prepare_socket(sock); if (error != GRPC_ERROR_NONE) { goto failure; } /* Grab the function pointer for ConnectEx for that specific socket. It may change depending on the interface. */ status = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &ioctl_num_bytes, NULL, NULL); if (status != 0) { error = GRPC_WSA_ERROR(WSAGetLastError(), "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER)"); goto failure; } grpc_sockaddr_make_wildcard6(0, &local_address); status = bind(sock, (struct sockaddr *)&local_address.addr, (int)local_address.len); if (status != 0) { error = GRPC_WSA_ERROR(WSAGetLastError(), "bind"); goto failure; } socket = grpc_winsocket_create(sock, "client"); info = &socket->write_info; success = ConnectEx(sock, (struct sockaddr *)&addr->addr, (int)addr->len, NULL, 0, NULL, &info->overlapped); /* It wouldn't be unusual to get a success immediately. But we'll still get an IOCP notification, so let's ignore it. */ if (!success) { int last_error = WSAGetLastError(); if (last_error != ERROR_IO_PENDING) { error = GRPC_WSA_ERROR(last_error, "ConnectEx"); goto failure; } } ac = gpr_malloc(sizeof(async_connect)); ac->on_done = on_done; ac->socket = socket; gpr_mu_init(&ac->mu); ac->refs = 2; ac->addr_name = grpc_sockaddr_to_uri(addr); ac->endpoint = endpoint; ac->resource_quota = resource_quota; grpc_closure_init(&ac->on_connect, on_connect, ac); grpc_timer_init(exec_ctx, &ac->alarm, deadline, on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC)); grpc_socket_notify_on_write(exec_ctx, socket, &ac->on_connect); return; failure: GPR_ASSERT(error != GRPC_ERROR_NONE); char *target_uri = grpc_sockaddr_to_uri(addr); grpc_error *final_error = grpc_error_set_str( GRPC_ERROR_CREATE_REFERENCING("Failed to connect", &error, 1), GRPC_ERROR_STR_TARGET_ADDRESS, target_uri); GRPC_ERROR_UNREF(error); if (socket != NULL) { grpc_winsocket_destroy(socket); } else if (sock != INVALID_SOCKET) { closesocket(sock); } grpc_resource_quota_internal_unref(exec_ctx, resource_quota); grpc_exec_ctx_sched(exec_ctx, on_done, final_error, NULL); }
void grpc_tcp_client_connect(void(*cb)(void *arg, grpc_endpoint *tcp), void *arg, const struct sockaddr *addr, int addr_len, gpr_timespec deadline) { SOCKET sock = INVALID_SOCKET; BOOL success; int status; struct sockaddr_in6 addr6_v4mapped; struct sockaddr_in6 local_address; async_connect *ac; grpc_winsocket *socket = NULL; LPFN_CONNECTEX ConnectEx; GUID guid = WSAID_CONNECTEX; DWORD ioctl_num_bytes; const char *message = NULL; char *utf8_message; grpc_winsocket_callback_info *info; /* Use dualstack sockets where available. */ if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { addr = (const struct sockaddr *)&addr6_v4mapped; addr_len = sizeof(addr6_v4mapped); } sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { message = "Unable to create socket: %s"; goto failure; } if (!grpc_tcp_prepare_socket(sock)) { message = "Unable to set socket options: %s"; goto failure; } status = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &ioctl_num_bytes, NULL, NULL); if (status != 0) { message = "Unable to retreive ConnectEx pointer: %s"; goto failure; } grpc_sockaddr_make_wildcard6(0, &local_address); status = bind(sock, (struct sockaddr *) &local_address, sizeof(local_address)); if (status != 0) { message = "Unable to bind socket: %s"; goto failure; } socket = grpc_winsocket_create(sock); info = &socket->write_info; success = ConnectEx(sock, addr, addr_len, NULL, 0, NULL, &info->overlapped); if (!success) { int error = WSAGetLastError(); if (error != ERROR_IO_PENDING) { message = "ConnectEx failed: %s"; goto failure; } } ac = gpr_malloc(sizeof(async_connect)); ac->cb = cb; ac->cb_arg = arg; ac->socket = socket; gpr_mu_init(&ac->mu); ac->refs = 2; grpc_alarm_init(&ac->alarm, deadline, on_alarm, ac, gpr_now()); grpc_socket_notify_on_write(socket, on_connect, ac); return; failure: utf8_message = gpr_format_message(WSAGetLastError()); gpr_log(GPR_ERROR, message, utf8_message); gpr_free(utf8_message); if (socket) { grpc_winsocket_orphan(socket); } else if (sock != INVALID_SOCKET) { closesocket(sock); } cb(arg, NULL); }