Skip to content

Commit

Permalink
Add scheduler hook Addrinfo.getaddrinfo. (#4375)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Sutic <code@brunosutic.com>
  • Loading branch information
ioquatix and bruno- committed Jun 14, 2021
1 parent 688b217 commit 2792acc
Show file tree
Hide file tree
Showing 9 changed files with 626 additions and 278 deletions.
7 changes: 7 additions & 0 deletions doc/fiber.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ class Scheduler
def timeout_after(duration, klass, *arguments, &block)
end

# Resolve hostname to an array of IP addresses.
# This hook is optional.
# @parameter hostname [String] Example: "www.ruby-lang.org".
# @returns [Array] An array of IPv4 and/or IPv6 address strings that the hostname resolves to.
def address_resolve(hostname)
end

# Block the calling fiber.
# @parameter blocker [Object] What we are waiting on, informational only.
# @parameter timeout [Numeric | Nil] The amount of time to wait for in seconds.
Expand Down
435 changes: 225 additions & 210 deletions ext/socket/depend

Large diffs are not rendered by default.

125 changes: 89 additions & 36 deletions ext/socket/raddrinfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,40 +284,6 @@ numeric_getaddrinfo(const char *node, const char *service,
return EAI_FAIL;
}

int
rb_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct rb_addrinfo **res)
{
struct addrinfo *ai;
int ret;
int allocated_by_malloc = 0;

ret = numeric_getaddrinfo(node, service, hints, &ai);
if (ret == 0)
allocated_by_malloc = 1;
else {
#ifdef GETADDRINFO_EMU
ret = getaddrinfo(node, service, hints, &ai);
#else
struct getaddrinfo_arg arg;
MEMZERO(&arg, struct getaddrinfo_arg, 1);
arg.node = node;
arg.service = service;
arg.hints = hints;
arg.res = &ai;
ret = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getaddrinfo, &arg, RUBY_UBF_IO, 0);
#endif
}

if (ret == 0) {
*res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
(*res)->allocated_by_malloc = allocated_by_malloc;
(*res)->ai = ai;
}
return ret;
}

void
rb_freeaddrinfo(struct rb_addrinfo *ai)
{
Expand Down Expand Up @@ -498,12 +464,63 @@ port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr)
}
}

static int
rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service,
const struct addrinfo *hints, struct rb_addrinfo **res)
{
int error, res_allocated = 0, _additional_flags = 0;
long i, len;
struct addrinfo *ai, *ai_tail = NULL;
char *hostp;
char _hbuf[NI_MAXHOST];
VALUE ip_addresses_array, ip_address;

ip_addresses_array = rb_fiber_scheduler_address_resolve(scheduler, host);

if (ip_addresses_array == Qundef) {
// Returns EAI_FAIL if the scheduler hook is not implemented:
return EAI_FAIL;
} else if (ip_addresses_array == Qnil) {
len = 0;
} else {
len = RARRAY_LEN(ip_addresses_array);
}

for(i=0; i<len; i++) {
ip_address = rb_ary_entry(ip_addresses_array, i);
hostp = host_str(ip_address, _hbuf, sizeof(_hbuf), &_additional_flags);
error = numeric_getaddrinfo(hostp, service, hints, &ai);
if (error == 0) {
if (!res_allocated) {
res_allocated = 1;
*res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
(*res)->allocated_by_malloc = 1;
(*res)->ai = ai;
ai_tail = ai;
} else {
while (ai_tail->ai_next) {
ai_tail = ai_tail->ai_next;
}
ai_tail->ai_next = ai;
ai_tail = ai;
}
}
}

if (res_allocated) { // At least one valid result.
return 0;
} else {
return EAI_NONAME;
}
}

struct rb_addrinfo*
rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack)
{
struct rb_addrinfo* res = NULL;
struct addrinfo *ai;
char *hostp, *portp;
int error;
int error = 0;
char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
int additional_flags = 0;

Expand All @@ -515,7 +532,43 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h
}
hints->ai_flags |= additional_flags;

error = rb_getaddrinfo(hostp, portp, hints, &res);
error = numeric_getaddrinfo(hostp, portp, hints, &ai);
if (error == 0) {
res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
res->allocated_by_malloc = 1;
res->ai = ai;
} else {
VALUE scheduler = rb_fiber_scheduler_current();
int resolved = 0;

if (scheduler != Qnil && hostp && !(hints->ai_flags & AI_NUMERICHOST)) {
error = rb_scheduler_getaddrinfo(scheduler, host, portp, hints, &res);

if (error != EAI_FAIL) {
resolved = 1;
}
}

if (!resolved) {
#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);
#endif
if (error == 0) {
res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo));
res->allocated_by_malloc = 0;
res->ai = ai;
}
}
}

if (error) {
if (hostp && hostp[strlen(hostp)-1] == '\n') {
rb_raise(rb_eSocket, "newline at the end of hostname");
Expand Down
2 changes: 1 addition & 1 deletion ext/socket/rubysocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
#include "ruby/thread.h"
#include "ruby/util.h"
#include "sockport.h"
#include "ruby/fiber/scheduler.h"

#ifndef HAVE_TYPE_SOCKLEN_T
typedef int socklen_t;
Expand Down Expand Up @@ -313,7 +314,6 @@ struct rb_addrinfo {
struct addrinfo *ai;
int allocated_by_malloc;
};
int rb_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct rb_addrinfo **res);
void rb_freeaddrinfo(struct rb_addrinfo *ai);
VALUE rsock_freeaddrinfo(VALUE arg);
int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);
Expand Down
32 changes: 1 addition & 31 deletions ext/socket/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -1279,34 +1279,10 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _)
rb_raise(rb_eArgError, "array size should be 3 or 4, %ld given",
RARRAY_LEN(sa));
}
/* host */
if (NIL_P(host)) {
hptr = NULL;
}
else {
strncpy(hbuf, StringValueCStr(host), sizeof(hbuf));
hbuf[sizeof(hbuf) - 1] = '\0';
hptr = hbuf;
}
/* port */
if (NIL_P(port)) {
strcpy(pbuf, "0");
pptr = NULL;
}
else if (FIXNUM_P(port)) {
snprintf(pbuf, sizeof(pbuf), "%ld", NUM2LONG(port));
pptr = pbuf;
}
else {
strncpy(pbuf, StringValueCStr(port), sizeof(pbuf));
pbuf[sizeof(pbuf) - 1] = '\0';
pptr = pbuf;
}
hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM;
/* af */
hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af);
error = rb_getaddrinfo(hptr, pptr, &hints, &res);
if (error) goto error_exit_addr;
res = rsock_getaddrinfo(host, port, &hints, 0);
sap = res->ai->ai_addr;
salen = res->ai->ai_addrlen;
}
Expand Down Expand Up @@ -1336,12 +1312,6 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _)
}
return rb_assoc_new(rb_str_new2(hbuf), rb_str_new2(pbuf));

error_exit_addr:
saved_errno = errno;
if (res) rb_freeaddrinfo(res);
errno = saved_errno;
rsock_raise_socket_error("getaddrinfo", error);

error_exit_name:
saved_errno = errno;
if (res) rb_freeaddrinfo(res);
Expand Down
2 changes: 2 additions & 0 deletions include/ruby/fiber/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ VALUE rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io);
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length);
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length);

VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname);

RBIMPL_SYMBOL_EXPORT_END()

#endif /* RUBY_FIBER_SCHEDULER_H */
14 changes: 14 additions & 0 deletions scheduler.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ static ID id_io_read;
static ID id_io_write;
static ID id_io_wait;

static ID id_address_resolve;

void
Init_Fiber_Scheduler(void)
{
Expand All @@ -40,6 +42,8 @@ Init_Fiber_Scheduler(void)
id_io_read = rb_intern_const("io_read");
id_io_write = rb_intern_const("io_write");
id_io_wait = rb_intern_const("io_wait");

id_address_resolve = rb_intern_const("address_resolve");
}

VALUE
Expand Down Expand Up @@ -200,3 +204,13 @@ rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offs
// We should ensure string has capacity to receive data, and then resize it afterwards.
return rb_check_funcall(scheduler, id_io_write, 4, arguments);
}

VALUE
rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname)
{
VALUE arguments[] = {
hostname
};

return rb_check_funcall(scheduler, id_address_resolve, 1, arguments);
}
6 changes: 6 additions & 0 deletions test/fiber/scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,10 @@ def fiber(&block)

return fiber
end

def address_resolve(hostname)
Thread.new do
Addrinfo.getaddrinfo(hostname, nil).map(&:ip_address).uniq
end.value
end
end

0 comments on commit 2792acc

Please sign in to comment.