Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delegate Cleanables #1711

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ TESTS = \
db_properties_test \
db_table_properties_test \
autovector_test \
cleanable_test \
column_family_test \
table_properties_collector_test \
arena_test \
Expand Down Expand Up @@ -1147,6 +1148,9 @@ full_filter_block_test: table/full_filter_block_test.o $(LIBOBJECTS) $(TESTHARNE
log_test: db/log_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(AM_LINK)

cleanable_test: table/cleanable_test.o $(LIBOBJECTS) $(TESTHARNESS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks to me this is not needed

$(AM_LINK)

table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
$(AM_LINK)

Expand Down
2 changes: 1 addition & 1 deletion db/pinned_iterators_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace rocksdb {
// PinnedIteratorsManager will be notified whenever we need to pin an Iterator
// and it will be responsible for deleting pinned Iterators when they are
// not needed anymore.
class PinnedIteratorsManager {
class PinnedIteratorsManager : public Cleanable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now the cleanup code will be called in the destructor, I think we must change that so that it's called in ReleasePinnedData()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it's better to have a Cleanable data member

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can go either way but it would be great if we discuss it and have pros and cons listed. The pros of inheritance are:

  1. Simplicity: no need to add the functions and the glue them to the Cleanable data member.

What would be the cons of this approach, or what would the pros of data member approach?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just felt it's better to have it as a data member because everywhere in the code Cleanable was doing Cleanup in the destructor. now we support to do the cleanup and reuse the Cleanable object. It just felt to me that having this behavior more explicit (having a data member instead of inheritance) is better.
but final decision is up to you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not have a strong opinion here. Let's go with your instinct and make it a data member.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought it seems easier if we keep the current inheritance scheme. The reason is that currently we can easily pass PinnedIteratorManager as a Cleanable to DelegateCleanupsTo function:

biter.DelegateCleanupsTo(pinned_iters_mgr);
void Cleanable::DelegateCleanupsTo(Cleanable* other)

Without having PinnedIteratorManager manager we would have two choices:

  • Cleanable::DelegateCleanupsTo(PinnedIteratorManager* other), which could cause circular dependency and would need more code restructuring
  • PinnedIteratorManager::DelegateCleanupsFrom(Cleanable*) which would require making PinnedIteratorManager a friend class of Cleanable to be able to access its internals, which does not look clean to me.

public:
PinnedIteratorsManager() : pinning_enabled(false) {}
~PinnedIteratorsManager() {
Expand Down
3 changes: 3 additions & 0 deletions include/rocksdb/iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Cleanable {
// not abstract and therefore clients should not override it.
typedef void (*CleanupFunction)(void* arg1, void* arg2);
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
void DelegateCleanupsTo(Cleanable* other);

protected:
struct Cleanup {
Expand All @@ -45,6 +46,8 @@ class Cleanable {
Cleanup* next;
};
Cleanup cleanup_;
// It also becomes the owner of c
void RegisterCleanup(Cleanup* c);
};

class Iterator : public Cleanable {
Expand Down
27 changes: 5 additions & 22 deletions table/block_based_table_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1559,15 +1559,8 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
break;
} else {
BlockIter stack_biter;
if (pin_blocks) {
// We need to create the BlockIter on heap because we may need to
// pin it if we encounterd merge operands
biter = static_cast<BlockIter*>(
NewDataBlockIterator(rep_, read_options, iiter.value()));
} else {
biter = &stack_biter;
NewDataBlockIterator(rep_, read_options, iiter.value(), biter);
}
biter = &stack_biter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use remove BlockIter* biter in line 1542
and use BlockIter biter; here only. I think that how it used to be before I changed it

NewDataBlockIterator(rep_, read_options, iiter.value(), biter);

if (read_options.read_tier == kBlockCacheTier &&
biter->status().IsIncomplete()) {
Expand Down Expand Up @@ -1596,22 +1589,12 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
}
s = biter->status();

if (pin_blocks) {
if (get_context->State() == GetContext::kMerge) {
// Pin blocks as long as we are merging
pinned_iters_mgr->PinIterator(biter);
} else {
delete biter;
}
biter = nullptr;
} else {
// biter is on stack, Nothing to clean
if (pin_blocks && get_context->State() == GetContext::kMerge) {
// Pin blocks as long as we are merging
biter->DelegateCleanupsTo(pinned_iters_mgr);
}
}
}
if (pin_blocks && biter != nullptr) {
delete biter;
}
if (s.ok()) {
s = iiter.status();
}
Expand Down
31 changes: 31 additions & 0 deletions table/iterator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,37 @@ Cleanable::~Cleanable() {
}
}

// TODO(myabandeh): if the list is too long we should maintain a tail pointer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add the possible optimizations/bottlenecks we discussed offline in the comments here

void Cleanable::DelegateCleanupsTo(Cleanable* other) {
assert(other != nullptr);
if (cleanup_.function == nullptr) {
return;
}
Cleanup* c = &cleanup_;
other->RegisterCleanup(c->function, c->arg1, c->arg2);
c = c->next;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to remove line 64,65 and just enter the loop directly
this is also related to my comment of not doing cleanup inside RegisterCleanup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the process for delegating the first Cleanup is different from the rest: the first is an embedded object and its values needs to be copied while the rest are on heap and they can be simply be added to the destination linked list.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, my bad

while (c != nullptr) {
Cleanup* next = c->next;
other->RegisterCleanup(c);
c = next;
}
cleanup_.function = nullptr;
cleanup_.next = nullptr;
}

void Cleanable::RegisterCleanup(Cleanable::Cleanup* c) {
assert(c != nullptr);
if (cleanup_.function == nullptr) {
cleanup_.function = c->function;
cleanup_.arg1 = c->arg1;
cleanup_.arg2 = c->arg2;
delete c;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the caller should be responsible for cleanup, what do you think ?
It's also confusing that we cleanup in one branch and not in the other

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth between these two approaches and finally went with doing the delete inside RegisterCleanup. My argument was that there are two cases:

  1. RegisterCleanup takes the ownership and adds the object to its linkedlist
  2. RegisterCleanup does not need the object and want it deleted
    I found it more consistent if RegisterCleanup takes the full ownership and takes care of both cases. Otherwise it would take ownership when it needs the object and would not do that when it wanted to be deleted, which is an inconsistent behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me

} else {
c->next = cleanup_.next;
cleanup_.next = c;
}
}

void Cleanable::RegisterCleanup(CleanupFunction func, void* arg1, void* arg2) {
assert(func != nullptr);
Cleanup* c;
Expand Down