Skip to content

Producer API Design #10

@mhowlett

Description

@mhowlett

Creating an issue to track discussion around the Producer API design.

@edenhill raised the point that in some scenarios, the order in which DeliveryReport's are returned is important and the user of the library will want access to this information.

Here's how things work currently:

  1. librdkafka's conf_set_dr_msg_cb is set to point to the DeliveryReportCallbackImpl method in Producer to handle delivery report events.
  2. Producer.ProduceAsync is called to produce a message.
  3. This creates a TaskCompletionSource, a reference to which is passed to the librdkafka produce method via the opaque parameter. librdkafka's produce method doesn't block.
  4. The Producer class maintains a thread which calls the librdkafka poll method periodically.
  5. The DeliveryReportCallbackImpl gets called (on the poll thread managed by Producer) for every produce call that completes.
  6. The reference to the TaskCompletionSource is re-constructed and the Task associated with the produce call completed.

The user of the library can maintain a list of Tasks (this may be inconvenient in streaming scenarios?!?) and use methods like Task.WaitAll, Task.WaitAny, Task.WhenAny to get the DeliveryReports. But I'm pretty sure there is no straightforward way to be guaranteed to get at these in the order they were completed. For example, WaitAny will return the first Task in the supplied list that has completed. But what if there are two or more in the list and they were completed out of order? But assume this is doesn't happen - you call WaitAny, handle the DeliveryReport. Then you need to remove the associated task from the list (slow!). Then call WaitAny again. In that time, it's very likely more than one other task completed (potentially out of order).

It seems there is a solution: Jon Skeet documents how he made an OrderByCompletion method in his blog: https://codeblog.jonskeet.uk/category/eduasync/ But this approach has a lot of overhead - yet another array of size equal to the original list of Tasks for one thing. I don't see this as a viable solution.

Here are the alternative solutions that I can see:

  1. Maintain a sequence number in Producer. When the delivery report callback is called, increment this sequence number and include it in the delivery report. This solution might work ok if the user of the library is calling Task.WaitAll for example. Once all tasks are done, they can iterate through the list and check for out of order calls. Still, that'd be annoying.

  2. Have a variant of the ProduceAsync method that takes a callback parameter. This gets called on the Producers poll thread and blocks it. rdkafka-dotnet has this as a relatively recent addition I think (do we now understand why?!?). It's not especially idiomatic.

  3. Have an OnDeliveryReport event on Producer which gets raised (on the poll thread) whenever there is a DeliveryReport callback. It blocks the poll thread. This is less flexible than RdKafka -> Confluent.Kafka #2 but more idiomatic and I can't see why flexibility to add different callbacks to different produce calls will be a strong requirement in practice. It will be less efficient than RdKafka -> Confluent.Kafka #2. I think this is a nicer API than RdKafka -> Confluent.Kafka #2, because it requires no effort by the user unless they want to use it.

  4. Expose a .Poll method on Producer and don't have a separate thread managed by Producer doing the polling. Possibly give the user a choice between this having the producer manage the thread (similar to one of the options for Consumer). i.e. also have a .Start() method. The Start method would start a background thread, not block.

We could provide multiple options without affecting the API surface area very much. We could include a sequence number in all delivery reports + have an OnDeliveryReport + have both .Poll and .Start, possibly which to use (poll or start) set by a constructor argument that defaults to having a background thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions