-
Notifications
You must be signed in to change notification settings - Fork 455
CDRIVER-3625 monitor with a thread-per-server #607
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
CDRIVER-3625 monitor with a thread-per-server #607
Conversation
For a client pool, servers are now scanned independently using a separate thread per server. SRV polling is now done on a separate thread.
rr_data.min_ttl * 1000, MONGOC_TOPOLOGY_MIN_RESCAN_SRV_INTERVAL_MS); | ||
|
||
topology_valid = true; | ||
srv_fail: | ||
bson_free (rr_data.txt_record_opts); | ||
bson_free (prefixed_service); | ||
_mongoc_host_list_destroy_all (rr_data.hosts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leak appeared when rerunning the manual test for SRV polling.
@@ -262,10 +262,10 @@ mkfifo pipe || true | |||
if [ -e pipe ]; then | |||
set +o xtrace | |||
tee error.log < pipe & | |||
run_valgrind ./src/libmongoc/test-libmongoc -d -F test-results.json 2>pipe | |||
run_valgrind ./src/libmongoc/test-libmongoc --no-fork -d -F test-results.json 2>pipe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lets debug logs to print to stdout. There's likely a better way to
accomplish that. But the only downside of running with --no-fork I'm aware of is
that the first test failure fails the entire process. That did not seem like a
big loss to me. Though, I'm happy to change back and look for alternatives if
there's disagreement
@@ -93,7 +93,7 @@ case "$OS" in | |||
check_mongocryptd | |||
|
|||
chmod +x src/libmongoc/Debug/test-libmongoc.exe | |||
./src/libmongoc/Debug/test-libmongoc.exe $TEST_ARGS | |||
./src/libmongoc/Debug/test-libmongoc.exe $TEST_ARGS -d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried putting --no-fork here, but then stumbled on an existing bug: https://jira.mongodb.org/browse/CDRIVER-3637
const mongoc_host_list_t *host, | ||
void *user_data, | ||
bson_error_t *error) | ||
mongoc_client_connect (bool buffered, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server monitors do not have a mongoc_client_t, but need to create a stream to a server given just a host address.
mongoc_client_connect establishes connection with just the address and SSL options, and is used by mongoc_client_default_stream_initiator, which takes the SSL options from a mongoc_client_t.
bson_error_t *error) | ||
mongoc_client_connect (bool buffered, | ||
bool use_ssl, | ||
void *ssl_opts_void, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sidenote: I found it frustrating to work with mongoc_ssl_opt_t. Since it is conditionally defined, using it requires either hiding the type or using typedefs.
|
||
/* Called only from server monitor thread. | ||
* Caller must hold no locks (user's callback may lock topology mutex). | ||
* Locks APM mutex. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had not realized until later, but by scanning servers independently, if we did not lock, this would change the thread-safety requirements of the callbacks.
In the previous behavior, since all monitoring happened in one thread, only one SDAM callback would be called at a time.
To persist that behavior, I've added a separate mutex. This cannot use the topology mutex, since the user's callback may need to obtain the topology mutex (e.g. if they do an operation that requests a scan, does server selection, etc.)
} | ||
|
||
static bool | ||
_server_monitor_cmd_send (mongoc_server_monitor_t *server_monitor, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is closely modelled from mongoc-async-cmd.c, and will be modified to support streamable ismaster (will need to send OP_MSG exhaust cursors if the server monitor transitions to streaming).
* servers that were removed from the topology. | ||
*/ | ||
static void | ||
_topology_collect_errors (mongoc_topology_t *topology, bson_error_t *error_out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The single-threaded mongoc_topology_scanner_t keeps the last seen error per server scanned. This could include an error received on a server that was removed from the topology if it had been scanned during the full blocking scan.
But in multi-threaded, servers are scanned independently, there's no clear way to track errors of servers that were removed (how long would we keep those errors around for?).
Instead, take errors from the server descriptions in the topology. This aligns with the server selection spec's recommendation of how to return errors:
https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#server-selection-errors
@@ -412,6 +412,9 @@ log_handler (mongoc_log_level_t log_level, | |||
if (!suite->silent) { | |||
mongoc_log_default_handler (log_level, log_domain, message, NULL); | |||
} | |||
} else if (log_level == MONGOC_LOG_LEVEL_DEBUG && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, if -d is passed to test-libmongoc, MONGOC_DEBUG messages will be printed.
@@ -140,8 +137,10 @@ _test_topology_reconcile_rs (bool pooled) | |||
* server0 is selected, server1 is discovered and added to scanner. | |||
*/ | |||
BSON_ASSERT (selects_server (client, secondary_read_prefs, server0)); | |||
BSON_ASSERT ( | |||
get_node (client->topology, mock_server_get_host_and_port (server1))); | |||
if (!pooled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some tests reach inside the mongoc_topology_scanner_t to make assertions. This is only applicable for single threaded now.
@@ -456,13 +447,12 @@ test_topology_reconcile_from_handshake (void *ctx) | |||
* mode for good measure. | |||
*/ | |||
static void | |||
_test_topology_reconcile_retire (bool pooled) | |||
test_topology_reconcile_retire_single (void) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The single threaded scanner has a concept of "retired" nodes, for nodes removed in the middle of a full scan. There is no equivalent for multi-threaded, so this test is now limited to single-threaded.
int n_succeeded; | ||
int n_failed; | ||
int n_unknowns; | ||
bson_mutex_t mutex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These counters are modified in SDAM callbacks and checked by the test. It wasn't thread-safe before and was showing up when running TSAN locally. Now, updating and reading them happens behind a mutex.
@@ -1684,38 +1784,6 @@ test_cluster_time_updated_during_handshake () | |||
mongoc_uri_destroy (uri); | |||
} | |||
|
|||
/* returns the last time the topology completed a full scan. */ | |||
static int64_t | |||
_get_last_scan (mongoc_client_t *client) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test required some changing. _get_last_scan is no longer a thing for multi-threaded since servers are scanned independently.
@@ -1433,28 +1529,32 @@ _test_ismaster_retry_pooled (bool hangup, int n_failures) | |||
/* retry immediately (for testing, "immediately" means less than 250ms */ | |||
request = mock_server_receives_ismaster (server); | |||
ASSERT_CMPINT64 (bson_get_monotonic_time () - t, <, (int64_t) 250 * 1000); | |||
/* Since connection was established successfully, the server description is | |||
* not marked as Unknown until after a failed retry attempt. */ | |||
BSON_ASSERT (has_known_server (client)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous behavior was incorrect, a failure would mark as Unknown before attempting a retry: https://jira.mongodb.org/browse/CDRIVER-3529
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell, functionality looks good. I couldn't resist nitpicking, but I'll leave the decision on coding style to you.
/* Called from application threads | ||
* Caller must hold topology lock. | ||
* Locks topology description mutex to copy out server description errors. | ||
* CLEANUP: this has nothing to do with background monitoring. Move to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a note here: did you want to clean this up in this PR or in a follow-up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I moved it, but forgot to update the comment. Fixed.
if (server_monitor->apm_callbacks.server_heartbeat_started) { | ||
mongoc_apm_server_heartbeat_started_t event; | ||
event.host = &server_monitor->host; | ||
event.context = server_monitor->apm_context; | ||
bson_mutex_lock (&server_monitor->topology->apm_mutex); | ||
server_monitor->apm_callbacks.server_heartbeat_started (&event); | ||
bson_mutex_unlock (&server_monitor->topology->apm_mutex); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: using a guard clause helps reduce nesting, but I'm aware that most code here wraps logic in the condition instead of exiting early.
if (server_monitor->apm_callbacks.server_heartbeat_started) { | |
mongoc_apm_server_heartbeat_started_t event; | |
event.host = &server_monitor->host; | |
event.context = server_monitor->apm_context; | |
bson_mutex_lock (&server_monitor->topology->apm_mutex); | |
server_monitor->apm_callbacks.server_heartbeat_started (&event); | |
bson_mutex_unlock (&server_monitor->topology->apm_mutex); | |
} | |
if (!server_monitor->apm_callbacks.server_heartbeat_started) { | |
return; | |
} | |
mongoc_apm_server_heartbeat_started_t event; | |
event.host = &server_monitor->host; | |
event.context = server_monitor->apm_context; | |
bson_mutex_lock (&server_monitor->topology->apm_mutex); | |
server_monitor->apm_callbacks.server_heartbeat_started (&event); | |
bson_mutex_unlock (&server_monitor->topology->apm_mutex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, done.
const bson_t *reply, | ||
int64_t duration_usec) | ||
{ | ||
if (server_monitor->apm_callbacks.server_heartbeat_succeeded) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit above applies here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const bson_error_t *error, | ||
int64_t duration_usec) | ||
{ | ||
if (server_monitor->apm_callbacks.server_heartbeat_failed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit above applies here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
topology->min_heartbeat_frequency_msec; | ||
server_monitor->connect_timeout_ms = topology->connect_timeout_msec; | ||
server_monitor->uri = mongoc_uri_copy (topology->uri); | ||
/* TODO: Do not retrieve ssl opts from topology scanner. They should be stored |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a tracking ticket to make this change to were you planning to do that in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, created https://jira.mongodb.org/browse/CDRIVER-3682 to track this and updated the comment.
if (topology->scanner_state == MONGOC_TOPOLOGY_SCANNER_BG_RUNNING) { | ||
topology->scanner_state = MONGOC_TOPOLOGY_SCANNER_SHUTTING_DOWN; | ||
} else { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit for readability:
if (topology->scanner_state == MONGOC_TOPOLOGY_SCANNER_BG_RUNNING) { | |
topology->scanner_state = MONGOC_TOPOLOGY_SCANNER_SHUTTING_DOWN; | |
} else { | |
return; | |
} | |
if (topology->scanner_state != MONGOC_TOPOLOGY_SCANNER_BG_RUNNING) { | |
return; | |
} | |
topology->scanner_state = MONGOC_TOPOLOGY_SCANNER_SHUTTING_DOWN; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
MONGOC_ERROR_PROTOCOL, | ||
MONGOC_ERROR_PROTOCOL_INVALID_REPLY, | ||
"Could not decompress server reply"); | ||
return MONGOC_ASYNC_CMD_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong return. Will fix in next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments but otherwise LGTM. There's one method I commented on that doesn't appear to be touched by any of the testings though.
@@ -114,7 +114,7 @@ BSON_BEGIN_DECLS | |||
BSON_FUNC, \ | |||
__LINE__, \ | |||
#_n, \ | |||
_iov, \ | |||
(void *) _iov, \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this done for warnings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I was getting compiler warnings when compiling with tracing.
#define RS_RESPONSE_TO_ISMASTER(server, primary, has_tags, ...) \ | ||
rs_response_to_ismaster (server, primary, has_tags, __VA_ARGS__, NULL) | ||
#define RS_RESPONSE_TO_ISMASTER( \ | ||
server, max_wire_version, primary, has_tags, ...) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was max_wire_version
being named primary
a mistake? I don't really see why it was named that and not this in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, primary
was misnamed, and was really being used as the max_wire_version
. And the has_tags
boolean was being passed as the primary
boolean.
tf = tf_new (0); | ||
|
||
for (i = 1; i < 100; i++) { | ||
MONGOC_DEBUG ("i=%d", i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to leave this here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did not. Removed.
#undef MONGOC_LOG_DOMAIN | ||
#define MONGOC_LOG_DOMAIN "bg_monitor" | ||
|
||
static BSON_THREAD_FUN (srv_polling_run, topology_void) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any tests for this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, there are no automated tests. The SRV polling spec requires manual testing: https://github.com/mongodb/specifications/tree/master/source/polling-srv-records-for-mongos-discovery/tests which I ran through following these instructions: https://github.com/kevinAlbs/SRVPollingTesting. This did flush out some bugs. E.g. originally the polling function locked the topology mutex during the DNS call. This would block server selection and monitoring.
Unit testing this seemed out of scope for now. I think we'd need to somehow mock the DNS server, or introduce new test seams. But this could be additional motivation for introducing new testing infrastructure. I created https://jira.mongodb.org/browse/CDRIVER-3693 to track that.
Sidenote: there is a DRIVERS ticket for improving the SRV polling integration tests, https://jira.mongodb.org/browse/DRIVERS-1025
Prerequisite work for implementing streamable ismaster in multi-threaded.
Background and rationale is described in WRITING-5411.
For a client pool, servers are now scanned independently using a separate thread per server.
SRV polling is now done on a separate thread.