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

fix Redis connection sharing #421

Merged
merged 1 commit into from
Dec 6, 2023

Conversation

Ladicek
Copy link
Contributor

@Ladicek Ladicek commented Dec 1, 2023

When a single RedisConnection is shared among multiple contexts (e.g. verticles), pipelining doesn't work correctly, because there is no coordination for concurrent access. This commit fixes that by making sure that all requests are performed from the same context on which the NetSocket was created. If the current context is different from the NetSocket context, the request is emitted on the correct context, which means no concurrent access, all requests are serialized.

Fixes #394

@Ladicek Ladicek requested a review from vietj December 1, 2023 17:05
@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 1, 2023

@vietj I believe this implements what we discussed earlier today. I'd really like your review on this :-)

When done, I will backport the fix to 4.x.

@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 1, 2023

Humm. Will investigate the failure next week.

@Ladicek Ladicek force-pushed the fix-connection-sharing branch from 375d732 to 35b2e4d Compare December 4, 2023 11:00
@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 4, 2023

Turns out when I moved from NetClient.connect(SocketAddress) to NetClientInternal.connectInternal(ConnectOptions, Promise, ContextInternal), I didn't create the ConnectOptions fully -- I also have to pass the SSL options.

Fixed.

@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 4, 2023

One more point to discuss maybe, @vietj

I recognize this

@Override
public Future<Response> send(final Request request) {
  if (context == vertx.getContext()) {
    return doSend(request);
  } else {
    Promise<Response> promise = vertx.getOrCreateContext().promise();
    context.runOnContext(ignored -> {
      try {
        doSend(request).onComplete(promise);
      } catch (Exception e) {
        promise.fail(e);
      }
    });
    return promise.future();
  }
}

is fairly low-level. If I understand correctly, this

@Override
public Future<Response> send(final Request request) {
  Promise<Response> promise = vertx.getOrCreateContext().promise();
  context.emit(ignored -> {
    try {
      doSend(request).onComplete(promise);
    } catch (Exception e) {
      promise.fail(e);
    }
  });
  return promise.future();
}

would be a shorter equivalent, except that it always allocates an extra Promise/Future, even if the current context is the same as the original context. Which variant would be considered more idiomatic?

@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 4, 2023

I decided to backport to 4.x proactively, here goes: #424

@Ladicek Ladicek force-pushed the fix-connection-sharing branch from 833e231 to 29d2a9d Compare December 5, 2023 09:02
When a single `RedisConnection` is shared among multiple contexts (e.g. verticles),
pipelining doesn't work correctly, because there is no coordination for concurrent
access. This commit fixes that by making sure that all requests are performed from
the same context on which the `NetSocket` was created. If the current context is
different from the `NetSocket` context, the request is emitted on the correct
context, which means no concurrent access, all requests are serialized.
@Ladicek Ladicek force-pushed the fix-connection-sharing branch from 29d2a9d to e97ca14 Compare December 5, 2023 10:37
@vietj
Copy link
Contributor

vietj commented Dec 6, 2023

One more point to discuss maybe, @vietj

I recognize this

@Override
public Future<Response> send(final Request request) {
  if (context == vertx.getContext()) {
    return doSend(request);
  } else {
    Promise<Response> promise = vertx.getOrCreateContext().promise();
    context.runOnContext(ignored -> {
      try {
        doSend(request).onComplete(promise);
      } catch (Exception e) {
        promise.fail(e);
      }
    });
    return promise.future();
  }
}

is fairly low-level. If I understand correctly, this

@Override
public Future<Response> send(final Request request) {
  Promise<Response> promise = vertx.getOrCreateContext().promise();
  context.emit(ignored -> {
    try {
      doSend(request).onComplete(promise);
    } catch (Exception e) {
      promise.fail(e);
    }
  });
  return promise.future();
}

would be a shorter equivalent, except that it always allocates an extra Promise/Future, even if the current context is the same as the original context. Which variant would be considered more idiomatic?

where is the extra promise allocated ?

@Ladicek
Copy link
Contributor Author

Ladicek commented Dec 6, 2023

I rewrote to your suggestion with execute(), which is shortest and doesn't allocate any extra Promise, so I think it's best :-)

@vietj
Copy link
Contributor

vietj commented Dec 6, 2023

I rewrote to your suggestion with execute(), which is shortest and doesn't allocate any extra Promise, so I think it's best :-)

ah right out of order messages :-)

@Ladicek Ladicek merged commit 305269e into vert-x3:master Dec 6, 2023
3 checks passed
@Ladicek Ladicek deleted the fix-connection-sharing branch December 6, 2023 14:42
@Ladicek Ladicek added this to the 5.0.0 milestone Jul 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants