Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby: Implement Client#discard! to cleanly dispose of connection post fork #65

Merged

Conversation

casperisfine
Copy link
Contributor

If you use Client#close on a connection that was inherited from a parent it will close the connection for the parent.

Instead we need to use some POSIX magic to close the file descriptor without emitting any close notification, so that the server isn't mislead into closing the connection on its side.

cc @matthewd as we just discussed this.
and cc @adrianna-chang-shopify as the trilogy-adapter will need to be updated to use that new method: https://github.com/github/activerecord-trilogy-adapter/blob/592d98f25ab3e21e0d4f0d9ebc51bb932fd547b3/lib/active_record/connection_adapters/trilogy_adapter.rb#L160

…fork

If you use `Client#close` on a connection that was inherited from a parent
it will close the connection for the parent.

Instead we need to use some POSIX magic to close the file descriptor
without emitting any close notification, so that the server isn't mislead
into closing the connection on its side.
@casperisfine casperisfine changed the title Ruby: Implement Client#discard to cleanly dispose of connection post fork Ruby: Implement Client#discard! to cleanly dispose of connection post fork Apr 5, 2023
Comment on lines 946 to 958
int sock_fd = trilogy_sock_fd(ctx->conn.socket);
if (sock_fd >= 0) {
int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0) {
trilogy_syserr_fail_str(errno, rb_str_new_cstr("Failed to open /dev/null"));
return Qfalse;
}

if (dup2(null_fd, sock_fd) < 0) {
trilogy_syserr_fail_str(errno, rb_str_new_cstr("dup2 failed"));
}
close(null_fd);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's better to have all this logic here, or it it would make sense to implement a trilogy_discard in the C library that could be used by other potential bindings?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it would make sense to add a method to the C API that handles duping the /dev/null file descriptor to the socket fd, and returns either TRILOGY_OK or TRILOGY_SYSERR, and then the cext could handle raising the ruby error.

Copy link
Collaborator

@adrianna-chang-shopify adrianna-chang-shopify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes make sense to me, although I do feel like we should move the logic to the C API.

int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0) {
trilogy_syserr_fail_str(errno, rb_str_new_cstr("Failed to open /dev/null"));
return Qfalse;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't raising in the prior line prevent us from returning false here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's unreachable, but help making clear we stop here.

Comment on lines 946 to 958
int sock_fd = trilogy_sock_fd(ctx->conn.socket);
if (sock_fd >= 0) {
int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0) {
trilogy_syserr_fail_str(errno, rb_str_new_cstr("Failed to open /dev/null"));
return Qfalse;
}

if (dup2(null_fd, sock_fd) < 0) {
trilogy_syserr_fail_str(errno, rb_str_new_cstr("dup2 failed"));
}
close(null_fd);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it would make sense to add a method to the C API that handles duping the /dev/null file descriptor to the socket fd, and returns either TRILOGY_OK or TRILOGY_SYSERR, and then the cext could handle raising the ruby error.

@casperisfine
Copy link
Contributor Author

@adrianna-chang-shopify done. There's now a trilogy_discard API which does all the heavy lifting.

Copy link
Collaborator

@adrianna-chang-shopify adrianna-chang-shopify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @casperisfine! Just one question, afterwards I'll merge.

struct trilogy_ctx *ctx = get_ctx(self);

if (ctx->conn.socket == NULL) {
return Qtrue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about returning true here instead of nil -- can you explain your reasoning?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a lot of reasoning honestly, I generally return true on success, but it depends a lot on context. But the return value doesn't matter much here TBH.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the return value doesn't matter much here TBH.

Yeah that's fair. I'm not sure I'd define calling discard! on an already-closed connection "successful", but doesn't matter a whole lot as you've said.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants