Skip to content

Instantly share code, notes, and snippets.

@mame
Created October 5, 2023 13:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mame/d7b4d8eaa3b0c3016838c33eea752cbb to your computer and use it in GitHub Desktop.
Save mame/d7b4d8eaa3b0c3016838c33eea752cbb to your computer and use it in GitHub Desktop.
diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb
index 93bd850c34..6eec869d69 100644
--- a/ext/socket/extconf.rb
+++ b/ext/socket/extconf.rb
@@ -327,6 +327,8 @@ def test_recvmsg_with_msg_peek_creates_fds(headers)
net/if_dl.h
arpa/nameser.h
resolv.h
+ pthread.h
+ sched.h
].each {|h|
if have_header(h, headers)
headers << h
@@ -700,6 +702,11 @@ def %(s) s || self end
end
end
+ have_func("pthread_create")
+ have_func("pthread_detach")
+ have_func("pthread_setaffinity_np")
+ have_func("sched_getcpu")
+
$VPATH << '$(topdir)' << '$(top_srcdir)'
create_makefile("socket")
end
diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c
index 269edc4dad..9f62b1f82c 100644
--- a/ext/socket/raddrinfo.c
+++ b/ext/socket/raddrinfo.c
@@ -10,6 +10,13 @@
#include "rubysocket.h"
+#if !defined(GETADDRINFO_EMU) && defined(HAVE_PTHREAD_CREATE) && defined(HAVE_PTHREAD_DETACH)
+// If pthread is available, we run getaddrinfo (or getnameinfo) in a dedicated pthread
+// to allow the resolution interruptible.
+# define GETADDRINFO_IN_DEDICATED_PTHREAD
+# include "ruby/thread_native.h"
+#endif
+
#if defined(INET6) && (defined(LOOKUP_ORDER_HACK_INET) || defined(LOOKUP_ORDER_HACK_INET6))
#define LOOKUP_ORDERS (sizeof(lookup_order_table) / sizeof(lookup_order_table[0]))
static const int lookup_order_table[] = {
@@ -176,18 +183,83 @@ parse_numeric_port(const char *service, int *portp)
#ifndef GETADDRINFO_EMU
struct getaddrinfo_arg
{
- const char *node;
- const char *service;
- const struct addrinfo *hints;
- struct addrinfo **res;
+ char *node;
+ char *service;
+ struct addrinfo hints;
+ struct addrinfo *res;
+ int executed;
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ int wait, done, ret, refcount;
+ rb_nativethread_lock_t mutex;
+ rb_nativethread_cond_t cond;
+#endif
};
+struct getaddrinfo_arg *
+init_getaddrinfo(char *hostp, char *portp, struct addrinfo *hints)
+{
+ struct getaddrinfo_arg *arg = malloc(sizeof(struct getaddrinfo_arg));
+ if (!arg) return NULL;
+ MEMZERO(arg, struct getaddrinfo_arg, 1);
+#undef strdup
+ if (hostp) {
+ arg->node = strdup(hostp);
+ if (!arg->node) {
+ free(arg);
+ return NULL;
+ }
+ }
+ else {
+ arg->node = 0;
+ }
+ if (portp) {
+ arg->service = strdup(portp);
+ if (!arg->service) {
+ free(arg->node);
+ free(arg);
+ return NULL;
+ }
+ }
+ else {
+ arg->service = 0;
+ }
+ arg->hints = *hints;
+ return arg;
+}
+
+static void
+finish_getaddrinfo(struct getaddrinfo_arg *arg, int need_lock, int need_free_addrinfo)
+{
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ if (need_lock) rb_native_mutex_lock(&arg->mutex);
+ if (0 == --arg->refcount) {
+ rb_native_mutex_unlock(&arg->mutex);
+ rb_native_cond_destroy(&arg->cond);
+ rb_native_mutex_destroy(&arg->mutex);
+ if (arg->node) free(arg->node);
+ if (arg->service) free(arg->service);
+ if (need_free_addrinfo) {
+ freeaddrinfo(arg->res);
+ }
+ free(arg);
+ }
+ else {
+ rb_native_mutex_unlock(&arg->mutex);
+ }
+#else
+ free(arg->node);
+ free(arg->service);
+ free(arg);
+#endif // GETADDRINFO_IN_DEDICATED_PTHREAD
+}
+
static void *
nogvl_getaddrinfo(void *arg)
{
int ret;
struct getaddrinfo_arg *ptr = arg;
- ret = getaddrinfo(ptr->node, ptr->service, ptr->hints, ptr->res);
+ ptr->executed = true;
+ ret = getaddrinfo(ptr->node, ptr->service, &ptr->hints, &ptr->res);
#ifdef __linux__
/* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and
* it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420]
@@ -197,7 +269,54 @@ nogvl_getaddrinfo(void *arg)
#endif
return (void *)(VALUE)ret;
}
+
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+static void *
+do_getaddrinfo(void *arg)
+{
+ struct getaddrinfo_arg *ptr = arg;
+ ptr->ret = (int)(VALUE)nogvl_getaddrinfo(arg);
+#ifdef __linux__
+ /* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and
+ * it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420]
+ */
+ if (ptr->ret == EAI_SYSTEM && errno == ENOENT)
+ ptr->ret = EAI_NONAME;
#endif
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->done = 1;
+ rb_native_cond_signal(&ptr->cond);
+ finish_getaddrinfo(ptr, 0, ptr->wait == 2);
+ return 0;
+}
+
+static void
+cancel_getaddrinfo(void *arg)
+{
+ struct getaddrinfo_arg *ptr = arg;
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->done = 2;
+ rb_native_cond_signal(&ptr->cond);
+ rb_native_mutex_unlock(&ptr->mutex);
+}
+
+static void *
+wait_getaddrinfo(void *arg)
+{
+ struct getaddrinfo_arg *ptr = arg;
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->wait = 1;
+ while (!ptr->done) {
+ rb_native_cond_wait(&ptr->cond, &ptr->mutex);
+ }
+ ptr->wait = 2;
+ rb_native_mutex_unlock(&ptr->mutex);
+ return (void*)(VALUE)ptr->ret;
+}
+
+#endif // GETADDRINFO_IN_DEDICATED_PTHREAD
+
+#endif // GETADDRINFO_EMU
static int
numeric_getaddrinfo(const char *node, const char *service,
@@ -305,25 +424,116 @@ rb_freeaddrinfo(struct rb_addrinfo *ai)
#ifndef GETADDRINFO_EMU
struct getnameinfo_arg
{
- const struct sockaddr *sa;
+ struct sockaddr *sa;
socklen_t salen;
int flags;
char *host;
size_t hostlen;
char *serv;
size_t servlen;
+ int executed;
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ int wait, done, ret, refcount;
+ rb_nativethread_lock_t mutex;
+ rb_nativethread_cond_t cond;
+#endif
};
+struct getnameinfo_arg *
+init_getnameinfo_arg(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags)
+{
+ struct getnameinfo_arg *arg = malloc(sizeof(struct getnameinfo_arg));
+ MEMZERO(arg, struct getnameinfo_arg, 1);
+ if (!arg) return NULL;
+ char *mem = (char*) malloc(salen + hostlen + servlen);
+ if (!mem) {
+ free(arg);
+ return NULL;
+ }
+ arg->sa = (struct sockaddr *)mem;
+ memcpy(mem, sa, salen);
+ arg->salen = salen;
+ arg->host = mem + salen;
+ arg->hostlen = hostlen;
+ arg->serv = mem + salen + hostlen;
+ arg->servlen = servlen;
+ arg->flags = flags;
+ arg->executed = 0;
+ return arg;
+}
+
+static void
+finish_getnameinfo(struct getnameinfo_arg *ptr)
+{
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ rb_native_mutex_lock(&ptr->mutex);
+ if (0 == --ptr->refcount) {
+ rb_native_mutex_unlock(&ptr->mutex);
+ rb_native_cond_destroy(&ptr->cond);
+ rb_native_mutex_destroy(&ptr->mutex);
+ free(ptr->sa);
+ free(ptr);
+ }
+ else {
+ rb_native_mutex_unlock(&ptr->mutex);
+ }
+#else
+ free(ptr->sa);
+ free(ptr);
+#endif
+}
+
static void *
nogvl_getnameinfo(void *arg)
{
struct getnameinfo_arg *ptr = arg;
+ ptr->executed = 1;
return (void *)(VALUE)getnameinfo(ptr->sa, ptr->salen,
ptr->host, (socklen_t)ptr->hostlen,
ptr->serv, (socklen_t)ptr->servlen,
ptr->flags);
}
-#endif
+
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+static void *
+do_getnameinfo(void *arg)
+{
+ struct getnameinfo_arg *ptr = arg;
+ ptr->ret = (int)(VALUE)nogvl_getnameinfo(arg);
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->done = 1;
+ rb_native_cond_signal(&ptr->cond);
+ rb_native_mutex_unlock(&ptr->mutex);
+ finish_getnameinfo(ptr);
+ return 0;
+}
+
+static void *
+wait_getnameinfo(void *arg)
+{
+ struct getnameinfo_arg *ptr = arg;
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->wait = 1;
+ while (!ptr->done) {
+ rb_native_cond_wait(&ptr->cond, &ptr->mutex);
+ }
+ rb_native_mutex_unlock(&ptr->mutex);
+ return (void*)(VALUE)ptr->ret;
+}
+
+static void
+cancel_getnameinfo(void *arg)
+{
+ struct getnameinfo_arg *ptr = arg;
+ rb_native_mutex_lock(&ptr->mutex);
+ ptr->done = 2;
+ rb_native_cond_signal(&ptr->cond);
+ rb_native_mutex_unlock(&ptr->mutex);
+}
+
+#endif // GETADDRINFO_IN_DEDICATED_PTHREAD
+
+#endif // GETADDRINFO_EMU
int
rb_getnameinfo(const struct sockaddr *sa, socklen_t salen,
@@ -333,16 +543,53 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen,
#ifdef GETADDRINFO_EMU
return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
#else
- struct getnameinfo_arg arg;
+start:
+ int retry = 0;
+ struct getnameinfo_arg *arg = init_getnameinfo_arg(sa, salen, host, hostlen, serv, servlen, flags);
+ if (!arg) return ENOMEM;
+
int ret;
- arg.sa = sa;
- arg.salen = salen;
- arg.host = host;
- arg.hostlen = hostlen;
- arg.serv = serv;
- arg.servlen = servlen;
- arg.flags = flags;
- ret = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getnameinfo, &arg, RUBY_UBF_IO, 0);
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ arg->done = 0;
+ arg->ret = 0;
+ arg->refcount = 2;
+ rb_native_mutex_initialize(&arg->mutex);
+ rb_native_cond_initialize(&arg->cond);
+
+ pthread_t t;
+ if (pthread_create(&t, 0, do_getnameinfo, arg) != 0) {
+ ret = EAGAIN;
+ }
+ else {
+ pthread_detach(t);
+#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_SCHED_GETCPU)
+ cpu_set_t tmp_cpu_set;
+ CPU_ZERO(&tmp_cpu_set);
+ CPU_SET(sched_getcpu(), &tmp_cpu_set);
+ pthread_setaffinity_np(t, sizeof(cpu_set_t), &tmp_cpu_set);
+#endif
+ ret = (int)(VALUE)rb_thread_call_without_gvl2(wait_getnameinfo, arg, cancel_getnameinfo, arg);
+ if (arg->wait == 0) {
+ ret = EAGAIN;
+ retry = 1;
+ }
+ }
+#else
+ ret = (int)(VALUE)rb_thread_call_without_gvl2(nogvl_getnameinfo, arg, RUBY_UBF_IO, 0);
+ if (arg->executed == 0) {
+ ret = EAGAIN;
+ retry = 1;
+ }
+
+#endif
+ if (ret == 0) {
+ memcpy(host, (char*)(arg->sa) + salen, hostlen);
+ memcpy(serv, (char*)(arg->sa) + salen + hostlen, servlen);
+ }
+ finish_getnameinfo(arg);
+ rb_thread_check_ints();
+ if (retry) goto start;
+
return ret;
#endif
}
@@ -532,6 +779,9 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h
}
hints->ai_flags |= additional_flags;
+start:
+ int retry = 0;
+
error = numeric_getaddrinfo(hostp, portp, hints, &ai);
if (error == 0) {
res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
@@ -553,22 +803,50 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h
#ifdef GETADDRINFO_EMU
error = getaddrinfo(hostp, portp, hints, &ai);
#else
- struct getaddrinfo_arg arg;
- MEMZERO(&arg, struct getaddrinfo_arg, 1);
- arg.node = hostp;
- arg.service = portp;
- arg.hints = hints;
- arg.res = &ai;
- error = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getaddrinfo, &arg, RUBY_UBF_IO, 0);
+ struct getaddrinfo_arg *arg = init_getaddrinfo(hostp, portp, hints);
+ if (!arg) {
+ error = ENOMEM;
+ goto end;
+ }
+#ifdef GETADDRINFO_IN_DEDICATED_PTHREAD
+ arg->done = 0;
+ arg->ret = 0;
+ arg->refcount = 2;
+ rb_native_mutex_initialize(&arg->mutex);
+ rb_native_cond_initialize(&arg->cond);
+
+ pthread_t t;
+ if (pthread_create(&t, 0, do_getaddrinfo, arg) != 0) {
+ error = EAGAIN;
+ goto end;
+ }
+ else {
+ pthread_detach(t);
+ error = (int)(VALUE)rb_thread_call_without_gvl2(wait_getaddrinfo, arg, cancel_getaddrinfo, arg);
+ if (arg->wait == 0) {
+ error = EAGAIN;
+ retry = 1;
+ }
+ }
+#else
+ error = (int)(VALUE)rb_thread_call_without_gvl2(nogvl_getaddrinfo, arg, RUBY_UBF_IO, 0);
+ if (!arg->executed) retry = 1;
#endif
+ ai = arg->res;
+#endif // GETADDRINFO_EMU
if (error == 0) {
res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
res->allocated_by_malloc = 0;
res->ai = ai;
}
+end:
+ if (arg) finish_getaddrinfo(arg, 1, 0);
}
}
+ rb_thread_check_ints();
+ if (retry) goto start;
+
if (error) {
if (hostp && hostp[strlen(hostp)-1] == '\n') {
rb_raise(rb_eSocket, "newline at the end of hostname");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment