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

Deprecates Sender for much simpler BytesMessageSender #244

Merged
merged 5 commits into from
Jan 14, 2024

Conversation

codefromthecrypt
Copy link
Member

Yesterday @shakuzen @anuraaga @kojilin and I chatted about how the async reporters never actually use the async code of the senders. The async side was only used by zipkin-server, as the async reporter design intentionally uses a blocking loop to flush the span backlog. So, this makes things simpler by only requiring a blocking implementation: BytesMessageSender.send (chosen to not create symbol collisions and similar to BytesMessageEncoder.

The side effect is that new senders can use this interface and completely avoid the complicated Call then execute chain, deferring to whatever the library-specific blocking path is. I have implemented this in armeria as a test, and it is absolutely easier.

As older spring libraries like sleuth may not upgrade, this maintains the old types until reporter 4. However, new senders can simply implement the new type and be done with it.

@codefromthecrypt
Copy link
Member Author

PS this will also make things a lot easier for porting. For example, the spring boot senders are pretty much all synchronous anyway and so the code can be shredded once this is released. e.g. https://github.com/spring-projects/spring-boot/blob/7851c2362eb53c989cbdda706576c729a016bc49/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java#L43

cc @mhalbritter

@codefromthecrypt
Copy link
Member Author

here's an example sender, for armeria, granted I didn't add gzip to it yet.. the lack of creating the Call objects makes it a lot easier as you'll see vs okhttp for example (not its fault, as it would also be simple if we could remove Call without deprecation here) #245

The sender component handles the last step of sending a list of encoded spans onto a transport.
This involves I/O, so you can call `Sender.check()` to check its health on a given frequency.
Copy link
Member Author

Choose a reason for hiding this comment

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

almost everything implemented this with a post to empty list, and in any case check was almost never called!

*
* @since 3.2
*/
public interface BytesMessageSender extends Closeable {
Copy link
Member Author

Choose a reason for hiding this comment

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

here's the type which defines only methods actually used

*/
public abstract class Sender extends Component {
@Deprecated
public abstract class Sender extends Component implements BytesMessageSender {
Copy link
Member Author

Choose a reason for hiding this comment

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

retrofitted Sender as BytesMessageSender so that non-updated code can drop in and work. This is also why BytesMessageSender is an interface, as we can't extend two classes

On yesterday's call with @shakuzen @anuraaga and @kojilin, we chatted
about how the async reporters never actually use the async code of the
senders. The async side was only used by zipkin-server, as the async
reporter design intentionally uses a blocking loop to flush the span
backlog. So, this makes things simpler by only requiring a blocking
implementation: `BytesMessageSender.send` (chosen to not create symbol
collisions and similar to `BytesMessageEncoder`.

The side effect is that new senders can use this interface and
completely avoid the complicated `Call` then `execute` chain, deferring
to whatever the library-specific blocking path is. I have implemented
this in armeria as a test, and it is absolutely easier.

As older spring libraries like sleuth may not upgrade, this maintains
the old types until reporter 4. However, new senders can simply
implement the new type and be done with it.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
@@ -273,7 +278,7 @@ void flush(BufferNextMessage<S> bundler) {
});

try {
sender.sendSpans(nextMessage).execute();
Copy link
Member Author

Choose a reason for hiding this comment

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

this was the only place where the sender was used for its real purpose in this repo. So, the net change here is just more efficient and straightforward of the same

* @param encodedSpans list of encoded spans.
* @throws IllegalStateException if {@link #close() close} was called.
*/
void send(List<byte[]> encodedSpans) throws IOException;
Copy link
Contributor

Choose a reason for hiding this comment

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

(This is just an opinion) The semantics of sending an empty list as to way to very the connection could be surprising - logically I think noop is expected. May be having a dedicated check / ping method is not as bad after all

Copy link
Member Author

Choose a reason for hiding this comment

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

I really regret having put this check in the first place as it was a relic from finagle failfast. It isn't used for anything, but it is totally the case that sending an empty list is permitted today. I would rather document an empty list is permitted since almost all impls of check had to send an empty list. This is better than forcing people to implement check which would only do the same, and then people think it is smarter than that.

Copy link
Member Author

Choose a reason for hiding this comment

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

please have a look at the new RATIONALE.md for notes on this!

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot @codefromthecrypt for very comprehensive clarification! (knowing context always pays off)

@reta
Copy link
Contributor

reta commented Jan 13, 2024

Looks pretty clean, but letting other folks (who were part of the discussion) to take a look

Adrian Cole added 2 commits January 14, 2024 08:32
doc
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Signed-off-by: Adrian Cole <adrian@tetrate.io>
@codefromthecrypt codefromthecrypt merged commit 22b0e37 into master Jan 14, 2024
3 checks passed
@codefromthecrypt codefromthecrypt deleted the new-stuff branch January 14, 2024 07:23
@codefromthecrypt
Copy link
Member Author

Thanks for the thought @reta and @anuraaga!

@codefromthecrypt
Copy link
Member Author

openzipkin/brave-example#104 shows this works, so I'll release it so people can play with it, also to help with the maintenance work ahead for the spring hosted senders.

@codefromthecrypt
Copy link
Member Author

codefromthecrypt commented Jan 14, 2024

Here's a working armeria sender in less than 50 lines including imports:

package brave.example;

import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpData;
import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.MediaType;
import java.io.IOException;
import java.util.List;
import zipkin2.reporter.BytesMessageEncoder;
import zipkin2.reporter.BytesMessageSender;
import zipkin2.reporter.ClosedSenderException;
import zipkin2.reporter.Encoding;

final class WebClientSender extends BytesMessageSender.Base {
  final WebClient zipkinApiV2SpansClient;
  volatile boolean closeCalled; // volatile as not called from the reporting thread.

  WebClientSender(WebClient zipkinApiV2SpansClient) {
    super(Encoding.JSON);
    this.zipkinApiV2SpansClient = zipkinApiV2SpansClient;
  }

  @Override public int messageMaxBytes() {
    return 500_000; // Use the most common HTTP default
  }

  @Override public void send(List<byte[]> encodedSpans) throws IOException {
    if (closeCalled) throw new ClosedSenderException();
    byte[] body = BytesMessageEncoder.JSON.encode(encodedSpans);
    HttpRequest request =
        HttpRequest.of(HttpMethod.POST, "", MediaType.JSON, HttpData.wrap(body));
    AggregatedHttpResponse response = zipkinApiV2SpansClient.blocking().execute(request);
    try (HttpData content = response.content()) {
      if (!response.status().isSuccess()) {
        if (content.isEmpty()) {
          throw new IOException("response failed: " + response);
        }
        throw new IOException("response failed: " + content.toStringAscii());
      }
    }
  }

  @Override public void close() {
    closeCalled = true;
  }
}

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