blob: 6d29a417d8a207fb30b6219574bcdfe1c2a6f380 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
#include <pulsar/defines.h>
#include <pulsar/st/Expected.h>
#include <pulsar/st/Future.h>
#include <memory>
namespace pulsar::st {
class TransactionImpl;
using TransactionImplPtr = std::shared_ptr<TransactionImpl>;
namespace detail {
class ClientCore;
class ProducerCore;
class StreamConsumerCore;
class QueueConsumerCore;
} // namespace detail
/**
* @brief Lifecycle states of a transaction (spec §9).
*
* A transaction starts `Open`, transitions through a transient `Committing` or
* `Aborting` phase while the outcome is being made durable, and ends in one of the
* terminal states `Committed`, `Aborted`, `Error`, or `TimedOut`. Query the
* current state with `Transaction::state()`.
*/
enum class TransactionState
{
Open, ///< Active: messages and acks may still be enlisted; not yet committed or aborted.
Committing, ///< Transient: a `commit()`/`commitAsync()` is in progress but not yet durable.
Aborting, ///< Transient: an `abort()`/`abortAsync()` is in progress but not yet finalized.
Committed, ///< Terminal: committed successfully; produced messages are visible and acks durable.
Aborted, ///< Terminal: aborted; produced messages are discarded and acks rolled back.
Error, ///< Terminal: a failure left the transaction in an unrecoverable state.
TimedOut, ///< Terminal: the transaction timeout elapsed before commit, so it was aborted.
};
/**
* @brief A transaction providing exactly-once semantics across multiple scalable
* topics and subscriptions (spec §9).
*
* Messages produced and acknowledgments made within a single transaction are
* applied atomically: on commit they all take effect, and on abort none of them
* do. This lets an application consume, transform, and produce across topics with
* no duplicates and no lost work.
*
* Typical usage:
* -# obtain a transaction from `PulsarClient::newTransaction()`;
* -# enlist publishes with `MessageBuilder::transaction(txn)` and acknowledgments
* with `consumer.acknowledge*(id, txn)`;
* -# call `commit()` to make the produced messages visible and the acks durable,
* or `abort()` to discard everything done within the transaction.
*
* A default-constructed `Transaction` is empty (falsy under `operator bool`) and
* must not be used to enlist or commit work.
*
* Holds the hidden `TransactionImpl`; its operations are defined in lib/st.
*/
class PULSAR_PUBLIC Transaction {
public:
/** @brief Construct an empty, unusable transaction (falsy under `operator bool`). */
Transaction() = default;
/** Copyable, movable handle; copies share the underlying transaction. */
Transaction(const Transaction&) = default;
Transaction& operator=(const Transaction&) = default;
Transaction(Transaction&&) = default;
Transaction& operator=(Transaction&&) = default;
/**
* @brief Return the current lifecycle state of this transaction.
*
* @return The current `TransactionState`.
*/
TransactionState state() const;
/**
* @brief Commit the transaction: make produced messages visible and acks durable.
*
* Atomically applies every publish and acknowledgment enlisted in this
* transaction. Blocks until the outcome is durable.
*
* @return `Expected<void>` holding success, or an `Error` if the commit failed.
*/
Expected<void> commit() { return commitAsync().get(); }
/**
* @brief Asynchronously commit the transaction.
*
* Non-blocking counterpart of `commit()`; the returned future completes when the
* commit is durable or fails.
*
* @return `Future<void>` resolving to success, or an `Error` on failure.
*/
Future<void> commitAsync() const;
/**
* @brief Abort the transaction: discard produced messages and roll back acks.
*
* Atomically discards every publish and acknowledgment enlisted in this
* transaction, as if none had happened. Blocks until the abort is finalized.
*
* @return `Expected<void>` holding success, or an `Error` if the abort failed.
*/
Expected<void> abort() { return abortAsync().get(); }
/**
* @brief Asynchronously abort the transaction.
*
* Non-blocking counterpart of `abort()`; the returned future completes when the
* abort is finalized or fails.
*
* @return `Future<void>` resolving to success, or an `Error` on failure.
*/
Future<void> abortAsync() const;
/**
* @brief Test whether this object wraps a live transaction.
*
* @return `true` for a transaction obtained from `PulsarClient::newTransaction()`;
* `false` for a default-constructed (empty) one.
*/
explicit operator bool() const { return static_cast<bool>(impl_); }
private:
// The client constructs a Transaction; the producer/consumer cores read its
// impl to enlist sends and acks. All of these live in lib/st.
friend class detail::ClientCore;
friend class detail::ProducerCore;
friend class detail::StreamConsumerCore;
friend class detail::QueueConsumerCore;
explicit Transaction(TransactionImplPtr impl) : impl_(std::move(impl)) {}
TransactionImplPtr impl_;
};
} // namespace pulsar::st