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

Implement defrag support for IValue. #1

Merged
merged 4 commits into from
Sep 3, 2024

Conversation

MeirShpilraien
Copy link

@MeirShpilraien MeirShpilraien commented Sep 3, 2024

The PR adds support for defrag on IValue. The defrag uses the provided DefragAllocator to reallocate all the memory of the IValue.

Shared String Support

In general, it is considered easy to reallocate all the pointer of the IValue the only exception is the shared strings which we can not simply reallocate and free the old pointer because there might be more references that points to it.

To solve this issue we provide a way to clean the shared strings dictionary. This will cause new strings to be allocated from scratch and old shared strings will be freed when there will have no more references.

Notice that this means that now a shared string might not actually be shared between all jsons. Two json might have the same strings but with a different instance. This means that we can no longer count on pointer value for strings hash function, we must perform the hash function on the string value itself.

The PR adds support for defrag on `IValue`.
The defrag uses the provided `DefragAllocator`
to reallocate all the memory of the `IValue`.

### Shared String Support

In general, it is considered easy to reallocate
all the pointer of the `IValue` the only exception
is the shared strings which we can not simply reallocate
and free the old pointer because there might be more references
that points to it.

To solve this issue we provide a way to clean the shared strings
dictionary. This will cause new strings to be allocated from scratch
and old shared strings will be freed when there will have no
more references.

Notice that this means that now a shared string might not actually
be shared between all jsons. Two json might have the same strings
but with a different instance. This means that we can no longer
count on pointer value for strings hash function, we must perform
the hash function on the string value itself.
MeirShpilraien added a commit to RedisJSON/RedisJSON that referenced this pull request Sep 3, 2024
The PR adds support for active defrag on RedisJSON. A pre condition for this PR is that the following PR's will be megred:

* [Redis defrad module API extentions](redis/redis#13509)
* [redismodule-rs support for active defrag API](RedisLabsModules/redismodule-rs#387)
* [IJSON support for defrag](RedisJSON/ijson#1)

The PR register defrag function on the json datatype and uses the new capability of ISON to defrag the key.

**Notice**:

* that increamental defrag of the json key is **not** support. In order to support it we need to implement the free_effort callback. This is not trivial and even if it was it would have cause the json object to potentially be freed when the GIL is not hold, which violate the assumption of our shared string implementation (it is not thread safe). We leave it for future improvment.

* If we run on a Redis version that do not support the defrag start callback, we can still partially support defrag. In that case the IJSON object will be defraged but the shared strings dictionaty will not be reinitialize. This basically means that shared strings will not be defraged.

Tests were added to cover the new functionality.
src/number.rs Outdated
Comment on lines 714 to 728
impl<A: DefragAllocator> Defrag<A> for INumber {
fn defrag(mut self, defrag_allocator: &mut A) -> Self {
let hd = self.header();
if hd.type_ == NumberType::Static {
return self;
}
unsafe {
let ptr = self.0.ptr().cast::<Header>();
self.0.set_ptr(
defrag_allocator.realloc_ptr(ptr.cast(), Self::layout((*ptr).type_).unwrap()),
)
};
self
}
}

Choose a reason for hiding this comment

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

Three comments:
Use expect instead of unwrap.
Would it be possible to get the ptr from the Header itself instead of going through the IValue::ptr?
And would it be possible to reuse the already extracted type instead of accessing it again through ptr?

eg something like:

Suggested change
impl<A: DefragAllocator> Defrag<A> for INumber {
fn defrag(mut self, defrag_allocator: &mut A) -> Self {
let hd = self.header();
if hd.type_ == NumberType::Static {
return self;
}
unsafe {
let ptr = self.0.ptr().cast::<Header>();
self.0.set_ptr(
defrag_allocator.realloc_ptr(ptr.cast(), Self::layout((*ptr).type_).unwrap()),
)
};
self
}
}
impl<A: DefragAllocator> Defrag<A> for INumber {
fn defrag(mut self, defrag_allocator: &mut A) -> Self {
let hd = self.header();
match hd.type_ {
NumberType::Static => self,
t => {
let ptr = hd.ptr();
let layout = Self::layout(t).expect(...);
unsafe {
self.0.set_ptr(defrag_allocator.realloc_ptr(ptr.cast(), layout));
}
self
}
}
}
}

src/object.rs Outdated
return self;
}
let header = unsafe { self.header_mut() };
let slit_header = header.split_mut();

Choose a reason for hiding this comment

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

split_header

@@ -160,10 +168,10 @@ impl IString {
.pad_to_align())
}

fn alloc(s: &str) -> *mut Header {
fn alloc<A: FnMut(Layout) -> *mut u8>(s: &str, mut allocator: A) -> *mut Header {

Choose a reason for hiding this comment

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

Should the Allocator not be FnOnce?

@@ -175,29 +183,31 @@ impl IString {
}
}

fn dealloc(ptr: *mut Header) {
fn dealloc<D: FnMut(*mut u8, Layout)>(ptr: *mut Header, mut deallocator: D) {

Choose a reason for hiding this comment

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

FnOnce

/// Converts a `&str` to an `IString` by interning it in the global string cache.
#[must_use]
pub fn intern(s: &str) -> Self {
fn intern_with_allocator<A: FnMut(Layout) -> *mut u8>(s: &str, allocator: A) -> Self {

Choose a reason for hiding this comment

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

FnOnce

@@ -242,26 +252,36 @@ impl IString {
unsafe { self.0.raw_copy() }
}
}
pub(crate) fn drop_impl(&mut self) {

fn drop_impl_with_deallocator<D: FnMut(*mut u8, Layout)>(&mut self, deallocator: D) {

Choose a reason for hiding this comment

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

FnOnce

assert!((s.len() as u64) < (1 << 48));
unsafe {
let ptr = alloc(Self::layout(s.len()).unwrap()).cast::<Header>();
let ptr = allocator(Self::layout(s.len()).unwrap()).cast::<Header>();

Choose a reason for hiding this comment

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

expect

src/array.rs Outdated
unsafe {
let new_ptr = defrag_allocator.realloc_ptr(
self.0.ptr(),
Self::layout((*self.0.ptr().cast::<Header>()).cap).unwrap(),

Choose a reason for hiding this comment

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

expect

src/value.rs Outdated
@@ -208,6 +210,35 @@ pub enum ValueType {
unsafe impl Send for IValue {}
unsafe impl Sync for IValue {}

impl<A: DefragAllocator> Defrag<A> for IValue {
fn defrag(self, defrag_allocator: &mut A) -> Self {
match self.type_() {

Choose a reason for hiding this comment

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

match self.destructure()

src/value.rs Outdated
Comment on lines 631 to 647
// Safety: Must be an array
unsafe fn to_array_unchecked(self) -> IArray {
IArray(self)
}

// Safety: Must be an array
unsafe fn to_object_unchecked(self) -> IObject {
IObject(self)
}

unsafe fn to_string_unchecked(self) -> IString {
IString(self)
}

unsafe fn to_number_unchecked(self) -> INumber {
INumber(self)
}

Choose a reason for hiding this comment

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

Are these necessary anymore?

@MeirShpilraien MeirShpilraien merged commit eede48f into unsafe_string Sep 3, 2024
MeirShpilraien added a commit to RedisJSON/RedisJSON that referenced this pull request Sep 10, 2024
The PR adds support for active defrag on RedisJSON. A pre condition for this PR is that the following PR's will be megred:

* [Redis defrad module API extentions](redis/redis#13509)
* [redismodule-rs support for active defrag API](RedisLabsModules/redismodule-rs#387)
* [IJSON support for defrag](RedisJSON/ijson#1)

The PR register defrag function on the json datatype and uses the new capability of ISON to defrag the key.

**Notice**:

* Increamental defrag of the json key is **not** support. In order to support it we need to implement the free_effort callback. This is not trivial and even if it was it would have cause the json object to potentially be freed when the GIL is not hold, which violate the assumption of our shared string implementation (it is not thread safe). We leave it for future improvment.

* If we run on a Redis version that do not support the defrag start callback, we can still partially support defrag. In that case the IJSON object will be defraged but the shared strings dictionatrywill not be reinitialize. This basically means that shared strings will not be defraged.

Tests were added to cover the new functionality.
MeirShpilraien added a commit to RedisJSON/RedisJSON that referenced this pull request Sep 16, 2024
The PR adds support for active defrag on RedisJSON. A pre condition for this PR is that the following PR's will be megred:

* [Redis defrad module API extentions](redis/redis#13509)
* [redismodule-rs support for active defrag API](RedisLabsModules/redismodule-rs#387)
* [IJSON support for defrag](RedisJSON/ijson#1)

The PR register defrag function on the json datatype and uses the new capability of ISON to defrag the key.

**Notice**:

* Increamental defrag of the json key is **not** support. In order to support it we need to implement the free_effort callback. This is not trivial and even if it was it would have cause the json object to potentially be freed when the GIL is not hold, which violate the assumption of our shared string implementation (it is not thread safe). We leave it for future improvment.

* If we run on a Redis version that do not support the defrag start callback, we can still partially support defrag. In that case the IJSON object will be defraged but the shared strings dictionatrywill not be reinitialize. This basically means that shared strings will not be defraged.

Tests were added to cover the new functionality.

(cherry picked from commit 6539e1e)
MeirShpilraien added a commit to RedisJSON/RedisJSON that referenced this pull request Sep 16, 2024
The PR adds support for active defrag on RedisJSON. A pre condition for this PR is that the following PR's will be megred:

* [Redis defrad module API extentions](redis/redis#13509)
* [redismodule-rs support for active defrag API](RedisLabsModules/redismodule-rs#387)
* [IJSON support for defrag](RedisJSON/ijson#1)

The PR register defrag function on the json datatype and uses the new capability of ISON to defrag the key.

**Notice**:

* Increamental defrag of the json key is **not** support. In order to support it we need to implement the free_effort callback. This is not trivial and even if it was it would have cause the json object to potentially be freed when the GIL is not hold, which violate the assumption of our shared string implementation (it is not thread safe). We leave it for future improvment.

* If we run on a Redis version that do not support the defrag start callback, we can still partially support defrag. In that case the IJSON object will be defraged but the shared strings dictionatrywill not be reinitialize. This basically means that shared strings will not be defraged.

Tests were added to cover the new functionality.

(cherry picked from commit 6539e1e)
MeirShpilraien added a commit to RedisJSON/RedisJSON that referenced this pull request Sep 16, 2024
The PR adds support for active defrag on RedisJSON. A pre condition for this PR is that the following PR's will be megred:

* [Redis defrad module API extentions](redis/redis#13509)
* [redismodule-rs support for active defrag API](RedisLabsModules/redismodule-rs#387)
* [IJSON support for defrag](RedisJSON/ijson#1)

The PR register defrag function on the json datatype and uses the new capability of ISON to defrag the key.

**Notice**:

* Increamental defrag of the json key is **not** support. In order to support it we need to implement the free_effort callback. This is not trivial and even if it was it would have cause the json object to potentially be freed when the GIL is not hold, which violate the assumption of our shared string implementation (it is not thread safe). We leave it for future improvment.

* If we run on a Redis version that do not support the defrag start callback, we can still partially support defrag. In that case the IJSON object will be defraged but the shared strings dictionatrywill not be reinitialize. This basically means that shared strings will not be defraged.

Tests were added to cover the new functionality.

(cherry picked from commit 6539e1e)
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.

2 participants