Skip to content

Conversation

@shibd
Copy link
Owner

@shibd shibd commented Dec 6, 2022

Fixes #

Master Issue: #

Motivation

Modifications

Verifying this change

  • Make sure that the change passes the CI checks.

(Please pick either of the following options)

This change is a trivial rework / code cleanup without any test coverage.

(or)

This change is already covered by existing tests, such as (please describe tests).

(or)

This change added tests and can be verified as follows:

(example:)

  • Added integration tests for end-to-end deployment with large payloads (10MB)
  • Extended integration test for recovery after broker failure

Documentation

  • doc-required
    (Your PR needs to update docs and you will update later)

  • doc-not-needed
    (Please explain why)

  • doc
    (Your PR contains doc changes)

  • doc-complete
    (Docs have been already added)

BewareMyPower and others added 13 commits November 9, 2022 12:43
…he#108)

Fixes apache#107

### Motivation

The `MessageId::getDataAsString()` API returns a `std::string` to the
application side. In most cases it's not an issue. However, when
building Windows DLLs with `LINK_STATIC=ON`, the library will be
built with `/MTd` or `/MT` option to link 3rd party dependencies
statically. In this case, the DLL and the application have different C
runtime libraries that allocate or deallocate memory. The returned
`std::string` object is allocated inside the DLL, while it will be
destroyed in the application. The destruction could crash because the
application C runtime cannot find the heap address from the C runtime in
DLL.

### Modifications

For MSVC debug build, change the API to return a const reference to
`std::string`. Then the original `std::string` object will be
deallocated inside the DLL.
)

Fixes apache#104

### Motivation

Currently, the consumer ack the last message when the chunked messages exceed maxPendingChunkMessages. This is wrong behavior. This may lead to unexpected data loss.

This PR also fixes serval issues related to maxPendingChunkedMessages:

https://github.com/apache/pulsar-client-cpp/blob/1f7fdb86c409c1486d160528137f10ce07dcf3b2/lib/ConsumerImpl.cc#L387-L407

In the current logic, there are two `putIfAbsent` operations here, and they are confusing. If a new chunk message is received, it will be added to the chunkedMessageCache. But if the size of the cache reaches the maxPendingChunkedMessages, it will remove at least 1 ctx from the cache due to `chunkedMessageCache_.size() - maxPendingChunkedMessage_ + 1`. But the message is then put into the cache again. This can lead to unnecessary ctx buffer memory allocations.

Here are some key point of this issue:
<img width="961" alt="image" src="https://user-images.githubusercontent.com/16974619/200777662-59c2e262-fb68-4f03-b1b0-8a312cb1ac58.png">


### Modifications

* Fix consumer acked the wrong message when pending chunked messages exceed maxPendingChunkMessages
* Fix wrong behavior when remove the ctx from the chunkedMessageCache.
…pache#115)

Fixes apache#100 

### Motivation

If your project builds through CMake and you try to add pulsar-client in some directory for build with whole project (i.e. third-party submodule) you can't do this because pulsar-client CMake uses CMAKE_*_DIR for configure, and this variable always related to top-level project.

### Modifications

Change CMAKE_(SOURCES|BINARY)_DIR to PROJECT_*_DIR for library and tests
Master issue: apache#87

### Motivation

To support batch index acknowledgment, we must provide a method to get
the batch size of a batched message ID.

### Modifications

Instead of adding another overload constructor to `MessageId`, this PR
adds a `MessageIdBuilder` class to construct the `MessageId` in a more
elegant way.

The original constructor is counterintuitive because the partition
index is the 1st argument.

https://github.com/apache/pulsar-client-cpp/blob/74ef1a01f5c7a4604d251de6d040c433f9bbf56b/include/pulsar/MessageId.h#L47

Therefore, this PR marks it as deprecated and replace all invocations of
it with the `MessageIdBuilder` usages.

To verify the `MessageId::batchSize()`, the following tests are
modified:
- `BatchMessageTest.testBatchSizeInBytes`: the batch size is always 2
  because of the `batchingMaxAllowedSizeInBytes` config.
- `MessageChunkingTest.testEndToEnd`: the batch size field is not set
  (default: 0) because batching is disabled.
…pache#118)

Fixes apache#82 


### Motivation

apache#82 

### Modifications

* Support seek by timestamp for the consumer
* Support seek by messageid and timestamp for the reader
…#121)

### Motivation

When I ran the tests of Python wrapper in my local env, I observed a
segmentation fault. See the key stacktrace:

```
#3  0x00007ffff6d742c5 in std::unique_lock<std::mutex>::lock() () from /usr/local/lib/python3.8/dist-packages/_pulsar.cpython-38-x86_64-linux-gnu.so
#4  0x00007ffff6d72523 in std::unique_lock<std::mutex>::unique_lock(std::mutex&) ()
   from /usr/local/lib/python3.8/dist-packages/_pulsar.cpython-38-x86_64-linux-gnu.so
#5  0x00007ffff67de193 in pulsar::ClientImpl::newRequestId (this=0x0) at /home/xyz/github.com/apache/pulsar-client-cpp/lib/ClientImpl.cc:644
#6  0x00007ffff685d2c2 in pulsar::ConsumerImpl::~ConsumerImpl (this=0x7fff9800f9e0, __in_chrg=<optimized out>)
    at /home/xyz/github.com/apache/pulsar-client-cpp/lib/ConsumerImpl.cc:116
```

In the destructor of `ConsumerImpl`, `client->newRequestId` might be
called. However, `client` might be a null pointer because it's returned
by `std::weak_ptr::lock()`.

### Modifications

Add null check to avoid the segfault.
…ows artifacts (apache#124)

Motivation
The debug artifact for the windows system is too large. They are only used for debugging and doesn't need to include in the release.
And it's better to zip tar the `windows-static` artifact to make it easier to download through the command line.

The final result looks like this: https://dist.apache.org/repos/dist/dev/pulsar/pulsar-client-cpp/pulsar-client-cpp-3.1.0-candidate-1/

Modification
* Skip include the windows debug artifact in the release
* Tar `windows-static` artifact in the release.
…properly (apache#125)

### Motivation

PartitionedProducerImpl do not close sub-producers properly when any sub-producer creation fails. Continuing to retry creating producer will eventually reach the maximum producer limit. It seems a regression caused by apache#54. 

When sub-producer creation fails, state_ is set to Failed. PartitionedProducerImpl::closeAsync only do cleanup when state_==Ready and sub-producers do not close when state_==Failed.

https://github.com/apache/pulsar-client-cpp/blob/f0268ecd29a6d0030b7d07379ec609884b4c14ff/lib/PartitionedProducerImpl.cc#L273-L276

### Modifications

Close sub-producers when state != Closed.
@shibd shibd closed this Dec 6, 2022
shibd pushed a commit that referenced this pull request Jan 13, 2023
Fixes apache#167

### Motivation

Here are some debugging info when the segfault happened in
`testCloseClient`. The outputs have been trimmed to make them clear.

An example crash at `async_write`:

```
#12 0x00007ffff7496dad in basic_stream_socket<...>::boost::asio::async_write /usr/include/boost/asio/impl/write.hpp:512
#13 0x00007ffff748e003 in ClientConnection::asyncWrite lib/ClientConnection.h:245
#14 0x00007ffff746e0b6 in ClientConnection::handleHandshake (this=0x555555e689d0) lib/ClientConnection.cc:502
```

Another example crash at `async_receive`:

```
#6  0x00007ffff7497247 in basic_stream_socket<...>::async_receive /usr/include/boost/asio/basic_stream_socket.hpp:677
#7  0x00007ffff748e647 in ClientConnection::asyncReceive lib/ClientConnection.h:258
#8  0x00007ffff746fa5d in ClientConnection::readNextCommand lib/ClientConnection.cc:606
```

The frame where it crashed:

```
245       if (descriptor_data->shutdown_)
(gdb) p descriptor_data
$2 = (boost::asio::detail::epoll_reactor::per_descriptor_data &) @0x555555e4a780: 0x0
```

We can see the socket descriptor is `nullptr`. The root cause is when
`async_receive` or `async_write` is called, the `io_service` object
might be closed. This case happened when `createProducerAsync` is
called, the actual producer creation continues in another thread, while
the `client.close()` happens in the current thread.

### Modifications

Check if the `ClientConnection` is closed before `async_receive` or
`async_write`. To avoid the use of lock, changing the `state_` field to
atomic.

### Verifications

```bash
./tests/pulsar-tests --gtest_filter='ClientTest.testCloseClient' --gtest_repeat=20
```

It never crashed after applying this patch.
shibd pushed a commit that referenced this pull request Feb 18, 2024
### Motivation

We observed server null `ClientConnection` accesses in test environment.
See the `this=0x0` outputs in the following two typical stacks.

```
#8  bytesWritten (this=0xb8, size=371) at lib/SharedBuffer.h:166
#9  pulsar::ClientConnection::handleRead (this=0x0, err=..., bytesTransferred=371, minReadSize=4) at lib/ClientConnection.cc:609
```

```
#12 0x00007f33202933d2 in unique_lock (__m=..., this=0x7f3311c82800) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/std_mutex.h:197
#13 pulsar::ClientConnection::sendPendingCommands (this=0x0) at lib/ClientConnection.cc:1071
#14 0x00007f3320293d2d in pulsar::ClientConnection::handleSendPair (this=0x0, err=...) at lib/ClientConnection.cc:1066
```

Though `shared_from_this()` is always passed to the `std::bind`
function, when the method of `ClientConnection` is called, the pointer
is still `null`.

### Modifications

First, replace all `std::bind` calls with the lambda expression that
catches `std::weak_ptr<ClientConnection>` and perform null checks
explicitly on the value returned by the `lock()` method.

Since now all asio callbacks don't hold a `shared_ptr`, the owner of
the `ClientConnection` object should be `ConnectionPool`, i.e. the pool
maintains some connections, while all asio callbacks use `weak_ptr` to
test if the connection is present.

Second, make `ClientConnection::getConnection` return `shared_ptr`
rather than `weak_ptr` so that the caller side does not need to check if
`lock()` returns null in the callback of this future.

We cannot make `ConnectionPool::getConnectionAsync` return `shared_ptr`
because it could return the future of `connectPromise_`, which is hold
by `ClientConnection` itself. We should avoid holding a `shared_ptr` of
`ClientConnection` because its owner is `ConnectionPool`.
shibd pushed a commit that referenced this pull request Feb 18, 2024
apache#334)

* Fix possible deadlock of Future when adding a listener after completed

### Motivation

There is a case that deadlock could happen for a `Future`. Assume there
is a `Promise` and its `Future`.

1. Call `Future::addListener` to add a listener that tries to acquire a
   user-provided mutex (`lock`).
2. Thread 1: Acquire `lock` first.
3. Thread 2: Call `Promise::setValue`, the listener will be triggered
   first before completed. Since `lock` is held by Thread 1, the
   listener will be blocked.
4. Thread 1: Call `Future::addListener`, since it detects the
   `InternalState::completed_` is true, it will call `get` to retrieve
   the result and value.

Then, deadlock happens:
- Thread 1 waits for `lock` is released, and then complete
  `InternalState::future_`.
- Thread 2 holds `lock` but wait for `InternalState::future_` is
  completed.

In a real world case, if we acquire a lock before
`ProducerImpl::closeAsync`, then another thread call `setValue` in
`ClientConnection::handleSuccess` and the callback of
`createProducerAsync` tries to acquire the lock, `handleSuccess` will be
blocked. Then in `closeAsync`, the current thread will be blocked in:

```c++
    cnx->sendRequestWithId(Commands::newCloseProducer(producerId_, requestId), requestId)
        .addListener([self, callback](Result result, const ResponseData&) { callback(result); });
```

The stacks:

```
Thread 1:
#11 0x00007fab80da2173 in pulsar::InternalState<...>::complete (this=0x3d53e7a10, result=..., value=...) at lib/Futre.h:61
#13 pulsar::ClientConnection::handleSuccess (this=this@entry=0x2214bc000, success=...) at lib/ClientConnection.cc:1552

Thread 2:
#8  get (result=..., this=0x3d53e7a10) at lib/Future.h:69
#9  pulsar::InternalState<...>::addListener (this=this@entry=0x3d53e7a10, listener=...) at lib/Future.h:51
#11 0x00007fab80e8dc4e in pulsar::ProducerImpl::closeAsync at lib/ProducerImpl.cc:794
```

There are two points that make the deadlock:
1. We use `completed_` to represent if the future is completed. However,
   after it's true, the future might not be completed because the value
   is not set and the listeners are not completed.
2. If `addListener` is called after it's completed, we still push the
   listener to `listeners_` so that previous listeners could be executed
   before the new listener. This guarantee is unnecessarily strong.

### Modifications

First, complete the future before calling the listeners.

Then, use an enum to represent the status:
- INITIAL: `complete` has not been called
- COMPLETING: when the 1st time `complete` is called, the status will
  change from INITIAL to COMPLETING
- COMPLETED: the future is completed.

Besides, implementation of `Future` is simplified.
apache#299 fixes a possible
mutex crash by introducing the `std::future`. However, the root cause is
the conditional variable is not used correctly:

> Even if the shared variable is atomic, it must be modified while owning the mutex to correctly publish the modification to the waiting thread.

See https://en.cppreference.com/w/cpp/thread/condition_variable

The simplest way to fix
apache#298 is just adding
`lock.lock()` before `state->condition.notify_all();`.

* Acquire lock again

* Add initial value
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.

6 participants