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

chore(core): add config hot-reload subsystem and implementation for pgwire credential changes #4168

Draft
wants to merge 119 commits into
base: master
Choose a base branch
from

Conversation

sklarsa
Copy link
Contributor

@sklarsa sklarsa commented Jan 27, 2024

This swaps out PropServerConfiguration usage with ReloadingPropServerConfiguration. It should be a no-op, in preparation for hot-reloading config

Instead of inheriting from PropServerConfiguration, I decided to wrap it in a new class to avoid moving code around too much, since the validation logic is in the PropServerConfiguration constructor

@sklarsa sklarsa changed the title chore(core): Replaces PropServerConfiguration with ReloadingPropServerConfiguration chore(core): replaces PropServerConfiguration with ReloadingPropServerConfiguration Jan 29, 2024
@sklarsa sklarsa changed the title chore(core): replaces PropServerConfiguration with ReloadingPropServerConfiguration chore(core): replace PropServerConfiguration with ReloadingPropServerConfiguration Jan 29, 2024
@ideoma
Copy link
Collaborator

ideoma commented Feb 2, 2024

[PR Coverage check]

😍 pass : 38 / 57 (66.67%)

file detail

path covered line new line coverage
🔵 io/questdb/ReloadingPropServerConfiguration.java 34 53 64.15%
🔵 io/questdb/PropBootstrapConfiguration.java 1 1 100.00%
🔵 io/questdb/cairo/DefaultCairoConfiguration.java 3 3 100.00%

Copy link
Member

@bluestreak01 bluestreak01 left a comment

Choose a reason for hiding this comment

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

also formatting is out :)

struct file_watcher *fw = (struct file_watcher *)wp;

// Clean up the inotify watch descriptor first
free(fw->wd);
Copy link
Member

Choose a reason for hiding this comment

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

this call looks extremely wonky, we need to free the following:

  • all mallocs, which pointers have been assigned to the struct
  • all fds that have been assigned

core/src/main/c/linux/filewatcher.c Outdated Show resolved Hide resolved

/* Re-copy the path so we can extract the filename */
strcpy(pathCpy, fp);
char *filename = malloc(sizeof(char) * strlen(pathCpy) + 1);
Copy link
Member

Choose a reason for hiding this comment

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

this leaks

struct file_watcher *fw;

/* Create the file descriptor for accessing the inotify API. */
const int fd = inotify_init();
Copy link
Member

Choose a reason for hiding this comment

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

this leaks

@Override
public void close() {
if (!closed) {
reloadThread.interrupt();
Copy link
Member

Choose a reason for hiding this comment

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

interrupt cannot be used to stop the thread, it is just a flag that is being set. Nor interrupt waits for the thread to stop. There should be a different mechanism, that guarantees thread is halted and also gurantees lack of race conditions.

I am wondering if closing fd would release the wait? After that the thread should wind down gracefully?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is a recent addition that I wasn't sure about. I'm removing the interrupt call, but still need to validate that all resources are properly released

}
fw->fd = fd;
fw->wd = wd;
fw->filename = filename;
Copy link
Member

Choose a reason for hiding this comment

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

i don't think we need to hold on to the filename

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 use filename to match filesystem events so we only emit them for that specific file:

if (strcmp(event->name, fw->filename))

inotify to watch the entire directory (and its contents) for changes
but only surface changes made to the watched file */
char pathCpy[strlen(fp)];
strcpy(pathCpy, fp);
Copy link
Member

Choose a reason for hiding this comment

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

we should not need a copy of string

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 found that when I didn't copy, the dirname call attempted to modify the fp string, which segfaulted because it is in protected memory. Per man pages:

Both dirname() and basename() may modify the contents of path, so it may be desirable to pass a copy when calling one of these functions.
https://linux.die.net/man/3/basename

I did find an extra, unnecessary strcpy which I deleted in 1264dac

public InotifyFileEventNotifier(CharSequence dirPath) {
try (Path p = new Path()) {
p.of(dirPath).$();
this.filewatcherPtr = setup(p.ptr());
Copy link
Member

Choose a reason for hiding this comment

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

the setup returns 0 in case of parameter error, we should at least have an assert here

import io.questdb.std.Misc;
import io.questdb.std.QuietCloseable;

public class FileWatcher implements QuietCloseable {
Copy link
Member

Choose a reason for hiding this comment

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

we need a test around file watcher, to make sure it can produce callbacks and can halt without segmentation fault

private boolean closed;

public InotifyFileEventNotifier(CharSequence dirPath) {
try (Path p = new Path()) {
Copy link
Member

Choose a reason for hiding this comment

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

can be replaced with Path.getThreadLocal()

@sklarsa sklarsa changed the title chore(core): replace PropServerConfiguration with ReloadingPropServerConfiguration chore(core): add config hot-reload subsystem and implementation for pgwire credential changes Apr 29, 2024
@sklarsa
Copy link
Contributor Author

sklarsa commented May 29, 2024

@bluestreak01 here's an update on this.

I just merged master into this branch, fixed merge conflicts, and all tests pass (with the exception of the flaky TestPgWireCredentialsReloadWithNewProp, related to this PR).

It looks like I was in the process of moving my inotify code into java to take advantage of the epoll helpers that we have. But then I moved on to other cloud-related things.

The Bootstrap and ServerMain plumbing code is working as expected for the DynamicServerConfiguration. I also have the DynamicUsernamePasswordMatcher which takes advantage of the dynamic server configuration for hot-reloading PGWire credentials.

As for the cross-platform filewatcher stuff, here is where we are.

OS Method Status
Linux Inotify In progress
Osx Kqueue Needs review (and recompilation)
Freebsd Kqueue Needs review (and recompilation)
Windows ? Not started

I'm happy to sync with you on this when you have time. I'd love to finally finish this thing.

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.

None yet

3 participants