Skip to content

Commit 18f9e17

Browse files
xdgacmorrow
authored andcommitted
CXX-1252 Tailable cursors only work once
The way cursors were marked dead to support multiple iterators on the same cursor had a side effect of preventing tailable cursors from working. This commit makes cursors know if they are tailable and rather than marking tailable cursors dead, they are marked "pending" so they can restart if re-iterated. (cherry picked from commit 32b1b26)
1 parent 502ca04 commit 18f9e17

File tree

5 files changed

+91
-14
lines changed

5 files changed

+91
-14
lines changed

src/mongocxx/collection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ cursor collection::find(view_or_value filter, const options::find& options) {
316316
libmongoc::cursor_set_max_await_time_ms(mongoc_cursor, static_cast<std::uint32_t>(count));
317317
}
318318

319-
return cursor{mongoc_cursor};
319+
return cursor{mongoc_cursor, options_converted.cursor_type()};
320320
}
321321

322322
stdx::optional<bsoncxx::document::value> collection::find_one(view_or_value filter,
@@ -921,7 +921,7 @@ cursor collection::distinct(bsoncxx::string::view_or_value field_name, view_or_v
921921
throw_exception<operation_exception>(error);
922922
}
923923

924-
return {fake_cursor};
924+
return cursor{fake_cursor};
925925
}
926926

927927
cursor collection::list_indexes() const {

src/mongocxx/cursor.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
namespace mongocxx {
3434
MONGOCXX_INLINE_NAMESPACE_BEGIN
3535

36-
cursor::cursor(void* cursor_ptr)
37-
: _impl(stdx::make_unique<impl>(static_cast<mongoc_cursor_t*>(cursor_ptr))) {
36+
cursor::cursor(void* cursor_ptr, bsoncxx::stdx::optional<cursor::type> cursor_type)
37+
: _impl(stdx::make_unique<impl>(static_cast<mongoc_cursor_t*>(cursor_ptr), cursor_type)) {
3838
}
3939

4040
cursor::cursor(cursor&&) noexcept = default;
@@ -56,7 +56,7 @@ cursor::iterator& cursor::iterator::operator++() {
5656
_cursor->_impl->mark_dead();
5757
throw_exception<query_exception>(error);
5858
} else {
59-
_cursor->_impl->mark_dead();
59+
_cursor->_impl->mark_nothing_left();
6060
_cursor = nullptr; // Set iterator equal to end().
6161
}
6262
return *this;

src/mongocxx/cursor.hpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <memory>
1818

1919
#include <bsoncxx/document/view.hpp>
20+
#include <bsoncxx/stdx/optional.hpp>
2021

2122
#include <mongocxx/config/prelude.hpp>
2223

@@ -53,6 +54,7 @@ class MONGOCXX_API cursor {
5354
///
5455
~cursor();
5556

57+
///
5658
/// A cursor::iterator that points to the beginning of any available
5759
/// results. If begin() is called more than once, the cursor::iterator
5860
/// returned points to the next remaining result, not the result of
@@ -64,7 +66,11 @@ class MONGOCXX_API cursor {
6466
///
6567
iterator begin();
6668

67-
/// A cursor::iterator that points to the end of the results.
69+
///
70+
/// A cursor::iterator that points to the end of the results. In the
71+
/// case of a tailable cursor, this iterator will compare equal to an
72+
/// exhausted tailable cursor iterator, even if more results are available
73+
/// the next time the cursor is iterated.
6874
///
6975
/// @return the cursor::iterator
7076
///
@@ -75,7 +81,8 @@ class MONGOCXX_API cursor {
7581
friend class client;
7682
friend class database;
7783

78-
MONGOCXX_PRIVATE cursor(void* cursor_ptr);
84+
MONGOCXX_PRIVATE cursor(void* cursor_ptr,
85+
bsoncxx::stdx::optional<type> cursor_type = bsoncxx::stdx::nullopt);
7986

8087
class MONGOCXX_PRIVATE impl;
8188
std::unique_ptr<impl> _impl;

src/mongocxx/private/cursor.hh

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#pragma once
1616

1717
#include <bsoncxx/document/view.hpp>
18+
#include <bsoncxx/stdx/optional.hpp>
1819
#include <mongocxx/cursor.hpp>
1920
#include <mongocxx/private/libmongoc.hh>
2021

@@ -25,12 +26,16 @@ MONGOCXX_INLINE_NAMESPACE_BEGIN
2526

2627
class cursor::impl {
2728
public:
28-
// States represent a one-way, ordered lifecycle of a cursor. k_started
29-
// means that libmongoc::cursor_next has been called at least once.
29+
// States represent a one-way, ordered lifecycle of a cursor. k_started means that
30+
// libmongoc::cursor_next has been called at least once. However, for a tailable
31+
// cursor, the cursor resets to k_pending on exhaustion so that it can resume later.
3032
enum class state { k_pending = 0, k_started = 1, k_dead = 2 };
3133

32-
impl(mongoc_cursor_t* cursor)
33-
: cursor_t(cursor), status{cursor ? state::k_pending : state::k_dead} {
34+
impl(mongoc_cursor_t* cursor, bsoncxx::stdx::optional<cursor::type> cursor_type)
35+
: cursor_t(cursor),
36+
status{cursor ? state::k_pending : state::k_dead},
37+
tailable{cursor && cursor_type && (*cursor_type == cursor::type::k_tailable ||
38+
*cursor_type == cursor::type::k_tailable_await)} {
3439
}
3540

3641
~impl() {
@@ -45,18 +50,28 @@ class cursor::impl {
4550
return status == state::k_dead;
4651
}
4752

48-
void mark_started() {
49-
status = state::k_started;
53+
bool is_tailable() const {
54+
return tailable;
5055
}
5156

5257
void mark_dead() {
53-
doc = bsoncxx::document::view{};
58+
mark_nothing_left();
5459
status = state::k_dead;
5560
}
5661

62+
void mark_nothing_left() {
63+
doc = bsoncxx::document::view{};
64+
status = tailable ? state::k_pending : state::k_dead;
65+
}
66+
67+
void mark_started() {
68+
status = state::k_started;
69+
}
70+
5771
mongoc_cursor_t* cursor_t;
5872
bsoncxx::document::view doc;
5973
state status;
74+
bool tailable;
6075
};
6176

6277
MONGOCXX_INLINE_NAMESPACE_END

src/mongocxx/test/collection.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <vector>
1919

20+
#include <bsoncxx/builder/basic/document.hpp>
2021
#include <bsoncxx/builder/stream/document.hpp>
2122
#include <bsoncxx/builder/stream/helpers.hpp>
2223
#include <bsoncxx/json.hpp>
@@ -35,6 +36,7 @@
3536
#include <mongocxx/read_concern.hpp>
3637
#include <mongocxx/write_concern.hpp>
3738

39+
using bsoncxx::builder::basic::kvp;
3840
using namespace bsoncxx::builder::stream;
3941
using namespace mongocxx;
4042

@@ -1692,6 +1694,59 @@ TEST_CASE("create_index tests", "[collection]") {
16921694
}
16931695
}
16941696

1697+
TEST_CASE("Tailable cursors", "[collection][tailable]") {
1698+
instance::current();
1699+
client mongodb_client{uri{}};
1700+
database db = mongodb_client["test"];
1701+
1702+
// Drop and (re)create a capped collection.
1703+
db["tailcheck"].drop();
1704+
auto create_opts = options::create_collection{}.capped(true).size(1024 * 1024);
1705+
db.create_collection("tailcheck", create_opts);
1706+
1707+
// Create tailable await cursor.
1708+
options::find opts;
1709+
1710+
SECTION("k_tailable") {
1711+
opts.cursor_type(cursor::type::k_tailable);
1712+
}
1713+
1714+
SECTION("k_tailable_await") {
1715+
opts.cursor_type(cursor::type::k_tailable_await);
1716+
}
1717+
1718+
INFO(std::string{opts.cursor_type() == cursor::type::k_tailable ? "k_tailable"
1719+
: "k_tailable_await"})
1720+
1721+
auto coll = db["tailcheck"];
1722+
auto cursor = coll.find({}, opts);
1723+
1724+
// Insert 3 documents.
1725+
for (int32_t n : {1, 2, 3}) {
1726+
coll.insert_one(document{} << "x" << n << finalize);
1727+
}
1728+
1729+
// Check that cursor finds three documents.
1730+
auto expected = 0;
1731+
1732+
for (auto&& doc : cursor) {
1733+
REQUIRE(doc["x"].get_int32() == ++expected);
1734+
}
1735+
1736+
// Insert 3 more documents.
1737+
for (int32_t n : {4, 5, 6}) {
1738+
coll.insert_one(document{} << "x" << n << finalize);
1739+
}
1740+
1741+
// Check that cursor finds next three documents.
1742+
for (auto&& doc : cursor) {
1743+
REQUIRE(doc["x"].get_int32() == ++expected);
1744+
}
1745+
1746+
// Check that cursor found all documents
1747+
REQUIRE(expected == 6);
1748+
}
1749+
16951750
TEST_CASE("regressions", "CXX-986") {
16961751
instance::current();
16971752
mongocxx::uri mongo_uri{"mongodb://non-existent-host.invalid/"};

0 commit comments

Comments
 (0)