Skip to content

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

Closed

Conversation

kevinAlbs
Copy link
Collaborator

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.

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);
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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,
Copy link
Collaborator Author

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,
Copy link
Collaborator Author

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.
Copy link
Collaborator Author

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,
Copy link
Collaborator Author

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)
Copy link
Collaborator Author

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 &&
Copy link
Collaborator Author

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) {
Copy link
Collaborator Author

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)
Copy link
Collaborator Author

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;
Copy link
Collaborator Author

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)
Copy link
Collaborator Author

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));
Copy link
Collaborator Author

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

@kevinAlbs kevinAlbs changed the title [WIP] CDRIVER-3625 monitor with a thread-per-server CDRIVER-3625 monitor with a thread-per-server May 7, 2020
@kevinAlbs kevinAlbs requested a review from bazile-clyde May 7, 2020 19:10
Copy link
Member

@alcaeus alcaeus left a 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
Copy link
Member

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?

Copy link
Collaborator Author

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.

Comment on lines 119 to 126
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);
}
Copy link
Member

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.

Suggested change
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);

Copy link
Collaborator Author

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) {
Copy link
Member

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.

Copy link
Collaborator Author

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) {
Copy link
Member

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.

Copy link
Collaborator Author

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
Copy link
Member

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?

Copy link
Collaborator Author

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.

Comment on lines 792 to 796
if (topology->scanner_state == MONGOC_TOPOLOGY_SCANNER_BG_RUNNING) {
topology->scanner_state = MONGOC_TOPOLOGY_SCANNER_SHUTTING_DOWN;
} else {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Nit for readability:

Suggested change
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;

Copy link
Collaborator Author

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;
Copy link
Collaborator Author

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.

Copy link
Contributor

@bazile-clyde bazile-clyde left a 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, \
Copy link
Contributor

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?

Copy link
Collaborator Author

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, ...) \
Copy link
Contributor

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.

Copy link
Collaborator Author

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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

@kevinAlbs kevinAlbs May 29, 2020

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

@kevinAlbs
Copy link
Collaborator Author

Fixed merge conflicts locally and pushed in 370fc18 (and cherry-picked to r1.17 in c62098b).

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