Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/pulsar/st/detail/ProducerCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace pulsar::st {
class ProducerImplBase;
using ProducerImplPtr = std::shared_ptr<ProducerImplBase>;
struct OutgoingMessage;
class ClientImpl; // lib/st — mints producer cores from createProducerAsync

namespace detail {

Expand All @@ -58,6 +59,7 @@ class PULSAR_PUBLIC ProducerCore {

private:
friend class ClientCore;
friend class ::pulsar::st::ClientImpl;
explicit ProducerCore(ProducerImplPtr impl) : impl_(std::move(impl)) {}

ProducerImplPtr impl_;
Expand Down
83 changes: 67 additions & 16 deletions lib/ClientImpl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ std::string generateRandomName() {

typedef std::vector<std::string> StringList;

// segment:// topics are the internal backing topics of a scalable topic and are
// reachable only through the pulsar::st client, which bypasses this rejection.
static Error segmentTopicRejected(const std::string& topic) {
return Error{ResultInvalidTopicName,
"segment:// topics are the internal backing topics of a scalable topic; use the "
"pulsar::st API instead: " +
topic};
}

static LookupServicePtr defaultLookupServiceFactory(const ServiceInfo& serviceInfo,
const ClientConfiguration& clientConfiguration,
ConnectionPool& pool) {
Expand Down Expand Up @@ -204,6 +213,21 @@ LookupServicePtr ClientImpl::getLookup(const std::string& redirectedClusterURI)

void ClientImpl::createProducerAsync(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback, bool autoDownloadSchema) {
createProducerAsyncImpl(topic, conf, std::move(callback), autoDownloadSchema,
/* allowSegmentTopic */ false);
}

void ClientImpl::createSegmentProducerAsync(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback,
const std::optional<std::string>& assignedBrokerUrl) {
createProducerAsyncImpl(topic, conf, std::move(callback), /* autoDownloadSchema */ false,
/* allowSegmentTopic */ true, assignedBrokerUrl);
}

void ClientImpl::createProducerAsyncImpl(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback, bool autoDownloadSchema,
bool allowSegmentTopic,
const std::optional<std::string>& assignedBrokerUrl) {
if (conf.isChunkingEnabled() && conf.getBatchingEnabled()) {
throw std::invalid_argument("Batching and chunking of messages can't be enabled together");
}
Expand All @@ -222,32 +246,38 @@ void ClientImpl::createProducerAsync(const std::string& topic, const ProducerCon
return;
}
}
if (topicName->isSegment() && !allowSegmentTopic) {
callback(segmentTopicRejected(topic));
return;
}

if (autoDownloadSchema) {
getSchema(topicName).addListener([self{shared_from_this()}, topicName, callback{std::move(callback)}](
const Error& error, const SchemaInfo& topicSchema) mutable {
if (error.result != ResultOk) {
callback(error);
return;
}
ProducerConfiguration conf;
conf.setSchema(topicSchema);
self->getPartitionMetadataAsync(topicName).addListener(
std::bind(&ClientImpl::handleCreateProducer, self, std::placeholders::_1,
std::placeholders::_2, topicName, conf, callback));
});
getSchema(topicName).addListener(
[self{shared_from_this()}, topicName, callback{std::move(callback)}, assignedBrokerUrl](
const Error& error, const SchemaInfo& topicSchema) mutable {
if (error.result != ResultOk) {
callback(error);
return;
}
ProducerConfiguration conf;
conf.setSchema(topicSchema);
self->getPartitionMetadataAsync(topicName).addListener(
std::bind(&ClientImpl::handleCreateProducer, self, std::placeholders::_1,
std::placeholders::_2, topicName, conf, callback, assignedBrokerUrl));
});
} else {
getPartitionMetadataAsync(topicName).addListener(
[this, conf, topicName, callback{std::move(callback)}](
[this, conf, topicName, callback{std::move(callback)}, assignedBrokerUrl](
const Error& error, const LookupDataResultPtr& partitionMetadata) {
handleCreateProducer(error, partitionMetadata, topicName, conf, callback);
handleCreateProducer(error, partitionMetadata, topicName, conf, callback, assignedBrokerUrl);
});
}
}

void ClientImpl::handleCreateProducer(const Error& error, const LookupDataResultPtr& partitionMetadata,
const TopicNamePtr& topicName, const ProducerConfiguration& conf,
CreateProducerV2Callback callback) {
CreateProducerV2Callback callback,
const std::optional<std::string>& assignedBrokerUrl) {
if (!error.result) {
ProducerImplBasePtr producer;

Expand All @@ -258,7 +288,10 @@ void ClientImpl::handleCreateProducer(const Error& error, const LookupDataResult
producer = std::make_shared<PartitionedProducerImpl>(
shared_from_this(), topicName, partitionMetadata->getPartitions(), conf, interceptors);
} else {
producer = std::make_shared<ProducerImpl>(shared_from_this(), *topicName, conf, interceptors);
producer =
std::make_shared<ProducerImpl>(shared_from_this(), *topicName, conf, interceptors,
/* partition */ -1,
/* retryOnCreationError */ false, assignedBrokerUrl);
}
} catch (const std::runtime_error& e) {
LOG_ERROR("Failed to create producer: " << e.what());
Expand Down Expand Up @@ -319,6 +352,10 @@ void ClientImpl::createReaderAsyncV2(const std::string& topic, const MessageId&
return;
}
}
if (topicName->isSegment()) {
callback(segmentTopicRejected(topic));
return;
}

getPartitionMetadataAsync(topicName).addListener(
[this, self{shared_from_this()}, topicName, startMessageId, conf, callback{std::move(callback)}](
Expand Down Expand Up @@ -348,6 +385,10 @@ void ClientImpl::createTableViewAsyncV2(const std::string& topic, const TableVie
return;
}
}
if (topicName->isSegment()) {
callback(segmentTopicRejected(topic));
return;
}

TableViewImplPtr tableViewPtr =
std::make_shared<TableViewImpl>(shared_from_this(), topicName->toString(), conf);
Expand Down Expand Up @@ -586,6 +627,11 @@ void ClientImpl::subscribeToTopicsAsyncV2(const std::string& topic, const std::s
}
}

if (topicName->isSegment()) {
callback(segmentTopicRejected(topic));
return;
}

getPartitionMetadataAsync(topicName).addListener(
[this, self{shared_from_this()}, topicName, subscriptionName, conf, callback{std::move(callback)}](
const auto& error, const auto& metadata) {
Expand Down Expand Up @@ -768,6 +814,11 @@ void ClientImpl::getPartitionsForTopicAsync(const std::string& topic, const GetP
return;
}
}
if (topicName->isSegment()) {
LOG_ERROR(segmentTopicRejected(topic));
callback(ResultInvalidTopicName, StringList());
return;
}
getPartitionMetadataAsync(topicName).addListener(
[this, self{shared_from_this()}, topicName, callback](const auto& error, const auto& metadata) {
handleGetPartitions(error.result, metadata, topicName, callback);
Expand Down
16 changes: 15 additions & 1 deletion lib/ClientImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ class ClientImpl : public std::enable_shared_from_this<ClientImpl> {
void createProducerAsync(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback, bool autoDownloadSchema = false);

/**
* Scalable topics (pulsar::st): create a producer on a segment:// backing
* topic, bypassing the segment-domain rejection applied to the public path.
*/
void createSegmentProducerAsync(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback,
const std::optional<std::string>& assignedBrokerUrl = std::nullopt);

void subscribeAsync(const std::string& topic, const std::string& subscriptionName,
const ConsumerConfiguration& conf, const SubscribeCallback& callback);

Expand Down Expand Up @@ -180,9 +188,15 @@ class ClientImpl : public std::enable_shared_from_this<ClientImpl> {
friend class PulsarFriend;

private:
void createProducerAsyncImpl(const std::string& topic, const ProducerConfiguration& conf,
CreateProducerV2Callback callback, bool autoDownloadSchema,
bool allowSegmentTopic,
const std::optional<std::string>& assignedBrokerUrl = std::nullopt);

void handleCreateProducer(const Error& error, const LookupDataResultPtr& partitionMetadata,
const TopicNamePtr& topicName, const ProducerConfiguration& conf,
CreateProducerV2Callback callback);
CreateProducerV2Callback callback,
const std::optional<std::string>& assignedBrokerUrl = std::nullopt);

void handleSubscribe(const Error& error, const LookupDataResultPtr& partitionMetadata,
const TopicNamePtr& topicName, const std::string& consumerName,
Expand Down
6 changes: 4 additions & 2 deletions lib/HandlerBase.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ HandlerBase::~HandlerBase() {
cancelTimer(*creationTimer_);
}

void HandlerBase::start() {
void HandlerBase::start() { start(std::nullopt); }

void HandlerBase::start(const optional<std::string>& assignedBrokerUrl) {
// guard against concurrent state changes such as closing
State state = NotStarted;
if (state_.compare_exchange_strong(state, Pending)) {
grabCnx();
grabCnx(assignedBrokerUrl);
}
creationTimer_->expires_after(operationTimeut_);
auto weakSelf = weak_from_this();
Expand Down
7 changes: 7 additions & 0 deletions lib/HandlerBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class HandlerBase : public std::enable_shared_from_this<HandlerBase> {

void start();

/*
* Start and, on the initial connect, go straight to @p assignedBrokerUrl instead of
* doing a topic lookup. Used by scalable-topics per-segment producers pinned to the
* DAG-provided owner broker. An empty optional behaves exactly like start().
*/
void start(const optional<std::string>& assignedBrokerUrl);

ClientConnectionWeakPtr getCnx() const;
void setCnx(const ClientConnectionPtr& cnx);
void resetCnx() { setCnx(nullptr); }
Expand Down
6 changes: 4 additions & 2 deletions lib/ProducerImpl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@ using std::chrono::milliseconds;

ProducerImpl::ProducerImpl(const ClientImplPtr& client, const TopicName& topicName,
const ProducerConfiguration& conf, const ProducerInterceptorsPtr& interceptors,
int32_t partition, bool retryOnCreationError)
int32_t partition, bool retryOnCreationError,
const optional<std::string>& assignedBrokerUrl)
: HandlerBase(client, (partition < 0) ? topicName.toString() : topicName.getTopicPartitionName(partition),
Backoff(milliseconds(client->getClientConfig().getInitialBackoffIntervalMs()),
milliseconds(client->getClientConfig().getMaxBackoffIntervalMs()),
milliseconds(std::max(100, conf.getSendTimeout() - 100)))),
conf_(conf),
semaphore_(),
partition_(partition),
assignedBrokerUrl_(assignedBrokerUrl),
producerName_(conf_.getProducerName()),
userProvidedProducerName_(false),
producerStr_("[" + topic() + ", " + producerName_ + "] "),
Expand Down Expand Up @@ -1007,7 +1009,7 @@ void ProducerImpl::disconnectProducer(const optional<std::string>& assignedBroke
void ProducerImpl::disconnectProducer() { disconnectProducer(std::nullopt); }

void ProducerImpl::start() {
HandlerBase::start();
HandlerBase::start(assignedBrokerUrl_);

if (conf_.getLazyStartPartitionedProducers() && conf_.getAccessMode() == ProducerConfiguration::Shared) {
// we need to kick it off now as it is possible that the connection may take
Expand Down
6 changes: 5 additions & 1 deletion lib/ProducerImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase {
ProducerImpl(const ClientImplPtr& client, const TopicName& topic,
const ProducerConfiguration& producerConfiguration,
const ProducerInterceptorsPtr& interceptors, int32_t partition = -1,
bool retryOnCreationError = false);
bool retryOnCreationError = false,
const optional<std::string>& assignedBrokerUrl = std::nullopt);
~ProducerImpl();

// overrided methods from ProducerImplBase
Expand Down Expand Up @@ -177,6 +178,9 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase {
std::list<std::unique_ptr<OpSendMsg>> pendingMessagesQueue_;

const int32_t partition_; // -1 if topic is non-partitioned
// When set, the initial connect goes straight to this broker (no lookup) — used by
// scalable-topics per-segment producers pinned to the DAG-provided owner broker.
const optional<std::string> assignedBrokerUrl_;
std::string producerName_;
bool userProvidedProducerName_;
std::string producerStr_;
Expand Down
34 changes: 31 additions & 3 deletions lib/TopicName.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace pulsar {

const std::string TopicDomain::Persistent = "persistent";
const std::string TopicDomain::NonPersistent = "non-persistent";
const std::string TopicDomain::Segment = "segment";
static const std::string PARTITION_NAME_SUFFIX = "-partition-";

typedef std::unique_lock<std::mutex> Lock;
Expand Down Expand Up @@ -105,7 +106,23 @@ bool TopicName::parse(const std::string& topicName, std::string& domain, std::st
domain = pathTokens[0];
size_t numSlashIndexes;
bool isV2Topic;
if (pathTokens.size() == 4) {
if (domain == TopicDomain::Segment) {
// segment://<tenant>/<ns>/<parent-topic>/<descriptor>: always the new
// (cluster-less) format, with a '/' inside the local name — so the
// token-count heuristic below must not mistake it for a legacy V1 name.
if (pathTokens.size() < 5) {
LOG_ERROR(
"Segment topic name is not valid, expected "
"segment://<tenant>/<namespace>/<topic>/<descriptor> - "
<< topicName);
return false;
}
property = pathTokens[1];
cluster = "";
namespacePortion = pathTokens[2];
numSlashIndexes = 3;
isV2Topic = true;
} else if (pathTokens.size() == 4) {
// New topic name without cluster name
property = pathTokens[1];
cluster = "";
Expand Down Expand Up @@ -171,7 +188,12 @@ bool TopicName::operator==(const TopicName& other) const {
bool TopicName::validate() {
// Check if domain matches with TopicDomain::Persistent, in future check "memory" when server is
// ready.
if (domain_.compare(TopicDomain::Persistent) != 0 && domain_.compare(TopicDomain::NonPersistent) != 0) {
if (domain_.compare(TopicDomain::Persistent) != 0 && domain_.compare(TopicDomain::NonPersistent) != 0 &&
domain_.compare(TopicDomain::Segment) != 0) {
return false;
}
if (domain_ == TopicDomain::Segment && !isV2Topic_) {
// Segment topics only exist in the new (cluster-less) format.
return false;
}
// cluster_ can be empty
Expand Down Expand Up @@ -228,7 +250,13 @@ std::string TopicName::toString() const {
return ss.str();
}

bool TopicName::isPersistent() const { return this->domain_ == TopicDomain::Persistent; }
// A segment topic is the persistent backing topic of one scalable-topic segment,
// so it counts as persistent (matching the Java TopicName).
bool TopicName::isPersistent() const {
return this->domain_ == TopicDomain::Persistent || this->domain_ == TopicDomain::Segment;
}

bool TopicName::isSegment() const { return this->domain_ == TopicDomain::Segment; }

std::string TopicName::getTopicPartitionName(unsigned int partition) const {
std::stringstream topicPartitionName;
Expand Down
6 changes: 6 additions & 0 deletions lib/TopicName.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class PULSAR_PUBLIC TopicDomain {
public:
static const std::string Persistent;
static const std::string NonPersistent;
// The per-segment backing topics of a scalable topic (PIP-460):
// segment://<tenant>/<ns>/<parent-topic>/<hashStart>-<hashEnd>-<segmentId>.
// Internal to the pulsar::st client; the classic user-facing entry points
// reject this domain.
static const std::string Segment;
}; // class TopicDomain

class PULSAR_PUBLIC TopicName : public ServiceUnitId {
Expand All @@ -62,6 +67,7 @@ class PULSAR_PUBLIC TopicName : public ServiceUnitId {
std::string getEncodedLocalName() const;
std::string toString() const;
bool isPersistent() const;
bool isSegment() const;
NamespaceNamePtr getNamespaceName();
int getPartitionIndex() const noexcept { return partition_; }
static std::shared_ptr<TopicName> get(const std::string& topicName);
Expand Down
Loading
Loading