Skip to content

Commit 97d8d9a

Browse files
authored
Fix memory leaks & Improve README (#24)
The `Test` Gh workflow is enhanced by also running the whole test suite with the `LEAK_CHECK` environment variable. While testing this functionality, it became apparent that we had memory leaks on `CBLCollection`, `CBLScope` & `CBLQueryIndex` objects. The functions `retain` and `wrap` were not clear enough on what they did, especially with the doc comments that were inverted. So the changes are: 1. Rename `retain` to `reference` 2. Rename `wrap` to `take_ownership` 3. Fix the doc comments on all functions 4. Fix the cases where retain and wrap were misused for `CBLCollection`, `CBLScope` & `CBLQueryIndex` 5. Implement `Drop` for `QueryIndex` The tests are now passing with the `LEAK_CHECK` environment variable. Additional changes: - The README is improved to document how to test - The README is improved to make editions (`community` & `enterprise`) more understandable - The file `src/main.rs` is deleted as this is a library
1 parent b664f4d commit 97d8d9a

File tree

14 files changed

+143
-131
lines changed

14 files changed

+143
-131
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ jobs:
2424
- uses: actions/checkout@v3
2525
- name: Run tests
2626
run: cargo test --features=${{ matrix.version }} --verbose
27+
- name: Run tests with Couchbase Lite C leak check
28+
run: LEAK_CHECK=y cargo test --features=${{ matrix.version }} --verbose -- --test-threads 1
2729
- name: Run tests (with address sanitizer)
2830
run: LSAN_OPTIONS=suppressions=san.supp RUSTFLAGS="-Zsanitizer=address" cargo +nightly test --features=${{ matrix.version }} --verbose

README.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,25 @@ Installation instructions are [here][BINDGEN_INSTALL].
2020

2121
### 2. Build!
2222

23-
You can use Couchbase Lite C community or entreprise editions:
23+
There two different editions of Couchbase Lite C: community & enterprise.
24+
You can find the differences [here][CBL_EDITIONS_DIFF].
25+
26+
When building or declaring this repository as a dependency, you need to specify the edition through a cargo feature:
2427

2528
```shell
26-
$ cargo build --features=enterprise
29+
$ cargo build --features=community
2730
```
2831

2932
```shell
30-
$ cargo build --features=community
33+
$ cargo build --features=enterprise
3134
```
3235

3336
## Maintaining
3437

3538
### Couchbase Lite For C
3639

3740
The Couchbase Lite For C shared library and headers ([Git repo][CBL_C]) are already embedded in this repo.
38-
They are present in the directory `libcblite`.
41+
They are present in two directories, one for each edition: `libcblite_community` & `libcblite_enterprise`.
3942

4043
### Upgrade Couchbase Lite C
4144

@@ -54,24 +57,46 @@ $ brew install wget
5457
$ brew install bash
5558
```
5659

57-
After that, fix the compilation & tests and you can create a pull request.
60+
If the script was successful:
61+
- Change the link `CBL_API_REFERENCE` in this README
62+
- Change the version in the test `couchbase_lite_c_version_test`
63+
- Update the version in `Cargo.toml`
64+
- Fix the compilation in both editions
65+
- Fix the tests in both editions
66+
- Create pull request
5867

5968
New C features should also be added to the Rust API at some point.
6069

6170
### Test
6271

63-
**The unit tests must be run single-threaded.** This is because each test case checks for leaks by
64-
counting the number of extant Couchbase Lite objects before and after it runs, and failing if the
65-
number increases. That works only if a single test runs at a time.
72+
Tests can be found in the `tests` subdirectory.
73+
Test are run in the GitHub wrokflow `Test`. You can find the commands used there.
74+
75+
There are three variations:
76+
77+
### Nominal run
6678

6779
```shell
68-
$ LEAK_CHECK=y cargo test -- --test-threads 1
80+
$ cargo test --features=enterprise
6981
```
7082

71-
### Sanitizer
83+
### Run with Couchbase Lite C leak check
84+
85+
Couchbase Lite C allows checking if instances of their objects are still alive through the functions `CBL_InstanceCount` & `CBL_DumpInstances`.
86+
If the `LEAK_CHECK` environment variable is set, we check that the number of instances at the end of each test is 0.
87+
88+
If this step fails in one of your pull requests, you should look into the `take_ownership`/`reference` logic on CBL pointers in the constructor of the Rust structs:
89+
- `take_ownership` takes ownership of the pointer, it will not increase the ref count of the `ref` CBL pointer so releasing it (in a `drop` for example) will free the pointer
90+
- `reference` just references the pointer, it will increase the ref count of CBL pointers so releasing it will not free the pointer
7291

7392
```shell
74-
$ LSAN_OPTIONS=suppressions=san.supp RUSTFLAGS="-Zsanitizer=address" cargo +nightly test
93+
$ LEAK_CHECK=y cargo test --features=enterprise -- --test-threads 1
94+
```
95+
96+
### Run with address sanitizer
97+
98+
```shell
99+
$ LSAN_OPTIONS=suppressions=san.supp RUSTFLAGS="-Zsanitizer=address" cargo +nightly test --features=enterprise
75100
```
76101

77102
## Learning
@@ -96,6 +121,8 @@ $ LSAN_OPTIONS=suppressions=san.supp RUSTFLAGS="-Zsanitizer=address" cargo +nigh
96121

97122
[CBL_API_REFERENCE]: https://docs.couchbase.com/mobile/3.2.1/couchbase-lite-c/C/html/modules.html
98123

124+
[CBL_EDITIONS_DIFF]: https://www.couchbase.com/products/editions/
125+
99126
[FLEECE]: https://github.com/couchbaselabs/fleece/wiki/Using-Fleece
100127

101128
[BINDGEN]: https://rust-lang.github.io/rust-bindgen/

src/collection.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,23 @@ impl Collection {
5656

5757
//////// CONSTRUCTORS:
5858

59-
/// Takes ownership of the object and increase it's reference counter.
60-
pub(crate) fn retain(cbl_ref: *mut CBLCollection) -> Self {
59+
/// Increase the reference counter of the CBL ref, so dropping the instance will NOT free the ref.
60+
pub(crate) fn reference(cbl_ref: *mut CBLCollection) -> Self {
6161
Self {
6262
cbl_ref: unsafe { retain(cbl_ref) },
6363
}
6464
}
6565

66+
/// Takes ownership of the CBL ref, the reference counter is not increased so dropping the instance will free the ref.
67+
pub(crate) const fn take_ownership(cbl_ref: *mut CBLCollection) -> Self {
68+
Self { cbl_ref }
69+
}
70+
6671
////////
6772

6873
/// Returns the scope of the collection.
6974
pub fn scope(&self) -> Scope {
70-
unsafe { Scope::retain(CBLCollection_Scope(self.get_ref())) }
75+
unsafe { Scope::take_ownership(CBLCollection_Scope(self.get_ref())) }
7176
}
7277

7378
/// Returns the collection name.
@@ -90,7 +95,7 @@ impl Collection {
9095

9196
/// Returns the collection's database.
9297
pub fn database(&self) -> Database {
93-
unsafe { Database::wrap(CBLCollection_Database(self.get_ref())) }
98+
unsafe { Database::reference(CBLCollection_Database(self.get_ref())) }
9499
}
95100

96101
/// Returns the number of documents in the collection.
@@ -136,7 +141,7 @@ impl Drop for Collection {
136141

137142
impl Clone for Collection {
138143
fn clone(&self) -> Self {
139-
Self::retain(self.get_ref())
144+
Self::reference(self.get_ref())
140145
}
141146
}
142147

@@ -150,7 +155,7 @@ unsafe extern "C" fn c_collection_change_listener(
150155
) {
151156
let callback = context as *const CollectionChangeListener;
152157
if let Some(change) = change.as_ref() {
153-
let collection = Collection::retain(change.collection as *mut CBLCollection);
158+
let collection = Collection::reference(change.collection as *mut CBLCollection);
154159
let doc_ids = std::slice::from_raw_parts(change.docIDs, change.numDocs as usize)
155160
.iter()
156161
.filter_map(|doc_id| doc_id.to_string())

src/database.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ unsafe extern "C" fn c_database_change_listener(
140140
c_doc_ids: *mut FLString,
141141
) {
142142
let callback = context as *const DatabaseChangeListener;
143-
let database = Database::retain(db as *mut CBLDatabase);
143+
let database = Database::reference(db as *mut CBLDatabase);
144144

145145
let doc_ids = std::slice::from_raw_parts(c_doc_ids, num_docs as usize)
146146
.iter()
@@ -159,7 +159,7 @@ unsafe extern "C" fn c_database_buffer_notifications(
159159
) {
160160
let callback: BufferNotifications = std::mem::transmute(context);
161161

162-
let database = Database::retain(db.cast::<CBLDatabase>());
162+
let database = Database::reference(db.cast::<CBLDatabase>());
163163

164164
callback(&database);
165165
}
@@ -180,15 +180,15 @@ impl CblRef for Database {
180180
impl Database {
181181
//////// CONSTRUCTORS:
182182

183-
/// Takes ownership of the object and increase it's reference counter.
184-
pub(crate) fn retain(cbl_ref: *mut CBLDatabase) -> Self {
183+
/// Increase the reference counter of the CBL ref, so dropping the instance will NOT free the ref.
184+
pub(crate) fn reference(cbl_ref: *mut CBLDatabase) -> Self {
185185
Self {
186186
cbl_ref: unsafe { retain(cbl_ref) },
187187
}
188188
}
189189

190-
/// References the object without taking ownership and increasing it's reference counter
191-
pub(crate) const fn wrap(cbl_ref: *mut CBLDatabase) -> Self {
190+
/// Takes ownership of the CBL ref, the reference counter is not increased so dropping the instance will free the ref.
191+
pub(crate) const fn take_ownership(cbl_ref: *mut CBLDatabase) -> Self {
192192
Self { cbl_ref }
193193
}
194194

@@ -217,7 +217,7 @@ impl Database {
217217
if db_ref.is_null() {
218218
return failure(err);
219219
}
220-
Ok(Self::wrap(db_ref))
220+
Ok(Self::take_ownership(db_ref))
221221
}
222222

223223
//////// OTHER STATIC METHODS:
@@ -418,7 +418,7 @@ impl Database {
418418
if scope.is_null() {
419419
None
420420
} else {
421-
Some(Scope::retain(scope))
421+
Some(Scope::take_ownership(scope))
422422
}
423423
})
424424
}
@@ -445,7 +445,7 @@ impl Database {
445445
if collection.is_null() {
446446
None
447447
} else {
448-
Some(Collection::retain(collection))
448+
Some(Collection::take_ownership(collection))
449449
}
450450
})
451451
}
@@ -474,7 +474,7 @@ impl Database {
474474
)
475475
};
476476

477-
check_error(&error).map(|()| Collection::retain(collection))
477+
check_error(&error).map(|()| Collection::take_ownership(collection))
478478
}
479479

480480
/// Delete an existing collection.
@@ -499,7 +499,7 @@ impl Database {
499499
let mut error = CBLError::default();
500500
let scope = unsafe { CBLDatabase_DefaultScope(self.get_ref(), &mut error) };
501501

502-
check_error(&error).map(|()| Scope::retain(scope))
502+
check_error(&error).map(|()| Scope::take_ownership(scope))
503503
}
504504

505505
/// Returns the default collection.
@@ -511,7 +511,7 @@ impl Database {
511511
if collection.is_null() {
512512
None
513513
} else {
514-
Some(Collection::retain(collection))
514+
Some(Collection::take_ownership(collection))
515515
}
516516
})
517517
}
@@ -526,7 +526,7 @@ impl Database {
526526
if collection.is_null() {
527527
Err(Error::cbl_error(CouchbaseLiteError::NotFound))
528528
} else {
529-
Ok(Collection::retain(collection))
529+
Ok(Collection::take_ownership(collection))
530530
}
531531
}
532532

@@ -596,6 +596,6 @@ impl Drop for Database {
596596

597597
impl Clone for Database {
598598
fn clone(&self) -> Self {
599-
Self::retain(self.get_ref())
599+
Self::reference(self.get_ref())
600600
}
601601
}

src/document.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ unsafe extern "C" fn c_conflict_handler(
7575
let callback: ConflictHandler = std::mem::transmute(context);
7676

7777
callback(
78-
&mut Document::retain(document_being_saved),
79-
&Document::retain(conflicting_document as *mut CBLDocument),
78+
&mut Document::reference(document_being_saved),
79+
&Document::reference(conflicting_document as *mut CBLDocument),
8080
)
8181
}
8282

@@ -92,7 +92,7 @@ unsafe extern "C" fn c_database_document_change_listener(
9292
c_doc_id: FLString,
9393
) {
9494
let callback = context as *const DatabaseDocumentChangeListener;
95-
let database = Database::retain(db as *mut CBLDatabase);
95+
let database = Database::reference(db as *mut CBLDatabase);
9696
(*callback)(&database, c_doc_id.to_string());
9797
}
9898

@@ -116,7 +116,7 @@ impl Database {
116116
failure(error)
117117
};
118118
}
119-
Ok(Document::wrap(doc))
119+
Ok(Document::take_ownership(doc))
120120
}
121121
}
122122

@@ -315,7 +315,7 @@ unsafe extern "C" fn c_collection_document_change_listener(
315315
) {
316316
let callback = context as *const CollectionDocumentChangeListener;
317317
if let Some(change) = change.as_ref() {
318-
let collection = Collection::retain(change.collection as *mut CBLCollection);
318+
let collection = Collection::reference(change.collection as *mut CBLCollection);
319319
(*callback)(collection, change.docID.to_string());
320320
}
321321
}
@@ -340,7 +340,7 @@ impl Collection {
340340
failure(error)
341341
};
342342
}
343-
Ok(Document::wrap(doc))
343+
Ok(Document::take_ownership(doc))
344344
}
345345
}
346346

@@ -515,26 +515,26 @@ impl Document {
515515
/// Creates a new, empty document in memory, with an automatically generated unique ID.
516516
/// It will not be added to a database until saved.
517517
pub fn new() -> Self {
518-
unsafe { Self::wrap(CBLDocument_Create()) }
518+
unsafe { Self::take_ownership(CBLDocument_Create()) }
519519
}
520520

521521
/// Creates a new, empty document in memory, with the given ID.
522522
/// It will not be added to a database until saved.
523523
pub fn new_with_id(id: &str) -> Self {
524-
unsafe { Self::wrap(CBLDocument_CreateWithID(from_str(id).get_ref())) }
524+
unsafe { Self::take_ownership(CBLDocument_CreateWithID(from_str(id).get_ref())) }
525525
}
526526

527-
/// Takes ownership of the object and increase it's reference counter.
528-
pub(crate) fn retain(cbl_ref: *mut CBLDocument) -> Self {
527+
/// Increase the reference counter of the CBL ref, so dropping the instance will NOT free the ref.
528+
pub(crate) fn reference(cbl_ref: *mut CBLDocument) -> Self {
529529
unsafe {
530530
Self {
531531
cbl_ref: retain(cbl_ref),
532532
}
533533
}
534534
}
535535

536-
/// References the object without taking ownership and increasing it's reference counter
537-
pub(crate) const fn wrap(cbl_ref: *mut CBLDocument) -> Self {
536+
/// Takes ownership of the CBL ref, the reference counter is not increased so dropping the instance will free the ref.
537+
pub(crate) const fn take_ownership(cbl_ref: *mut CBLDocument) -> Self {
538538
Self { cbl_ref }
539539
}
540540

@@ -607,6 +607,6 @@ impl Drop for Document {
607607

608608
impl Clone for Document {
609609
fn clone(&self) -> Self {
610-
Self::retain(self.get_ref())
610+
Self::reference(self.get_ref())
611611
}
612612
}

src/encryptable.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ impl CblRef for Encryptable {
6666

6767
impl From<*mut CBLEncryptable> for Encryptable {
6868
fn from(cbl_ref: *mut CBLEncryptable) -> Self {
69-
Self::retain(cbl_ref)
69+
Self::reference(cbl_ref)
7070
}
7171
}
7272

7373
impl Encryptable {
7474
//////// CONSTRUCTORS:
7575

76-
/// Takes ownership of the object and increase it's reference counter.
77-
pub(crate) fn retain(cbl_ref: *mut CBLEncryptable) -> Self {
76+
/// Increase the reference counter of the CBL ref, so dropping the instance will NOT free the ref.
77+
pub(crate) fn reference(cbl_ref: *mut CBLEncryptable) -> Self {
7878
Self {
7979
cbl_ref: unsafe { retain(cbl_ref) },
8080
}

src/fleece.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl Value {
323323
pub fn get_encryptable_value(&self) -> Encryptable {
324324
unsafe {
325325
let encryptable = FLDict_GetEncryptableValue(FLValue_AsDict(self.get_ref()));
326-
Encryptable::retain(encryptable as *mut CBLEncryptable)
326+
Encryptable::reference(encryptable as *mut CBLEncryptable)
327327
}
328328
}
329329

@@ -598,7 +598,7 @@ impl Dict {
598598
pub fn get_encryptable_value(&self) -> Encryptable {
599599
unsafe {
600600
let encryptable = FLDict_GetEncryptableValue(self.get_ref());
601-
Encryptable::retain(encryptable as *mut CBLEncryptable)
601+
Encryptable::reference(encryptable as *mut CBLEncryptable)
602602
}
603603
}
604604

0 commit comments

Comments
 (0)