-
-
Notifications
You must be signed in to change notification settings - Fork 249
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
Shared memory hash table #1379
base: develop
Are you sure you want to change the base?
Shared memory hash table #1379
Changes from 7 commits
926201c
afb25ab
31ddf43
edd3300
f47615a
b69a6dc
a168537
8ccbd74
6e088de
f490b99
fe95ef3
cd9d6cc
34760d2
87558bc
e077414
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,220 @@ | ||||||||||||
use crate::lwlock::*; | ||||||||||||
use crate::shmem::PgSharedMemoryInitialization; | ||||||||||||
use crate::PgSharedMem; | ||||||||||||
use crate::{pg_sys, PGRXSharedMemory}; | ||||||||||||
use std::ffi::c_void; | ||||||||||||
use uuid::Uuid; | ||||||||||||
|
||||||||||||
#[derive(Debug, Eq, PartialEq)] | ||||||||||||
#[non_exhaustive] | ||||||||||||
pub enum Error { | ||||||||||||
HashTableFull, | ||||||||||||
} | ||||||||||||
|
||||||||||||
#[derive(Copy, Clone)] | ||||||||||||
pub struct PgHashMapInner { | ||||||||||||
htab: *mut pg_sys::HTAB, | ||||||||||||
elements: u64, | ||||||||||||
} | ||||||||||||
|
||||||||||||
unsafe impl PGRXSharedMemory for PgHashMapInner {} | ||||||||||||
unsafe impl Send for PgHashMapInner {} | ||||||||||||
unsafe impl Sync for PgHashMapInner {} | ||||||||||||
|
||||||||||||
#[repr(align(8))] | ||||||||||||
#[derive(Copy, Clone, Debug)] | ||||||||||||
struct Key<K> { | ||||||||||||
// We copy it with std::ptr::copy, but we don't actually use the field | ||||||||||||
// in Rust, hence the warning. | ||||||||||||
#[allow(dead_code)] | ||||||||||||
key: K, | ||||||||||||
} | ||||||||||||
|
||||||||||||
#[repr(align(8))] | ||||||||||||
#[derive(Copy, Clone, Debug)] | ||||||||||||
struct Value<V> { | ||||||||||||
value: V, | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl Default for PgHashMapInner { | ||||||||||||
fn default() -> Self { | ||||||||||||
Self { htab: std::ptr::null_mut(), elements: 0 } | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
pub struct PgHashMap<K: Copy + Clone, V: Copy + Clone> { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type for new APIs shall not be prefixed with "Pg". It's Postgres, everything's Pg everywhere, all these types are namespaced via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ShmemHashMap would be fine. |
||||||||||||
htab: PgLwLock<PgHashMapInner>, | ||||||||||||
size: u64, | ||||||||||||
phantom_key: std::marker::PhantomData<K>, | ||||||||||||
phantom_value: std::marker::PhantomData<V>, | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Compute the hash for the key and it's pointer | ||||||||||||
/// to pass to the hash_search. Lock on HTAB should be taken, | ||||||||||||
/// although not strictly required I think. | ||||||||||||
macro_rules! key { | ||||||||||||
($key:expr, $htab:expr) => {{ | ||||||||||||
let key = Key { key: $key }; | ||||||||||||
let key_ptr: *const c_void = std::ptr::addr_of!(key) as *const Key<K> as *const c_void; | ||||||||||||
let hash_value = unsafe { pg_sys::get_hash_value($htab.htab, key_ptr) }; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment re: "this has to document the preconditions of this function so we know why it's safe to call". The answer of "there are virtually none and this is an in-principle safe function as long as we call it when loaded in the Postgres context" is fine. |
||||||||||||
|
||||||||||||
(key_ptr, hash_value) | ||||||||||||
}}; | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Get the value pointer. It's stored next to the key. | ||||||||||||
/// https://github.com/postgres/postgres/blob/1f998863b0bc6fc8ef3d971d9c6d2c29b52d8ba2/src/backend/utils/hash/dynahash.c#L246-L250 | ||||||||||||
macro_rules! value_ptr { | ||||||||||||
($entry:expr) => {{ | ||||||||||||
let value_ptr: *mut Value<V> = | ||||||||||||
unsafe { $entry.offset(std::mem::size_of::<Key<K>>().try_into().unwrap()) } | ||||||||||||
as *mut Value<V>; | ||||||||||||
|
||||||||||||
value_ptr | ||||||||||||
}}; | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl<K: Copy + Clone, V: Copy + Clone> PgHashMap<K, V> { | ||||||||||||
/// Create new PgHashMap. This still needs to be allocated with | ||||||||||||
/// `pg_shmem_init!` just like any other shared memory structure. | ||||||||||||
pub const fn new(size: u64) -> PgHashMap<K, V> { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's common for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should also be made clear why it's a u64 instead of the more common usize. Yes, the difference "doesn't matter", but the difference between those two types informs what purpose it serves. |
||||||||||||
PgHashMap { | ||||||||||||
htab: PgLwLock::new(), | ||||||||||||
size, | ||||||||||||
phantom_key: std::marker::PhantomData, | ||||||||||||
phantom_value: std::marker::PhantomData, | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Insert a key and value into the HashMap. If the key is already | ||||||||||||
/// present, it will be replaced. If the HashMap is full, return an error. | ||||||||||||
pub fn insert(&self, key: K, value: V) -> Result<(), Error> { | ||||||||||||
levkk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
let mut found = false; | ||||||||||||
let mut htab = self.htab.exclusive(); | ||||||||||||
let (key_ptr, hash_value) = key!(key, htab); | ||||||||||||
|
||||||||||||
let entry = unsafe { | ||||||||||||
pg_sys::hash_search_with_hash_value( | ||||||||||||
htab.htab, | ||||||||||||
key_ptr, | ||||||||||||
hash_value, | ||||||||||||
pg_sys::HASHACTION_HASH_FIND, | ||||||||||||
&mut found, | ||||||||||||
) | ||||||||||||
}; | ||||||||||||
|
||||||||||||
// If we don't do this check, pg will overwrite | ||||||||||||
// some random entry with our key/value... | ||||||||||||
if entry.is_null() && htab.elements == self.size { | ||||||||||||
return Err(Error::HashTableFull); | ||||||||||||
} | ||||||||||||
|
||||||||||||
let entry = unsafe { | ||||||||||||
pg_sys::hash_search_with_hash_value( | ||||||||||||
htab.htab, | ||||||||||||
key_ptr, | ||||||||||||
hash_value, | ||||||||||||
pg_sys::HASHACTION_HASH_ENTER_NULL, | ||||||||||||
&mut found, | ||||||||||||
) | ||||||||||||
}; | ||||||||||||
|
||||||||||||
if !entry.is_null() { | ||||||||||||
let value_ptr = value_ptr!(entry); | ||||||||||||
let value = Value { value }; | ||||||||||||
unsafe { | ||||||||||||
std::ptr::copy(std::ptr::addr_of!(value), value_ptr, 1); | ||||||||||||
} | ||||||||||||
htab.elements += 1; | ||||||||||||
Ok(()) | ||||||||||||
} else { | ||||||||||||
// OOM. | ||||||||||||
return Err(Error::HashTableFull); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Get a value from the HashMap using the key. | ||||||||||||
/// If the key doesn't exist, return None. | ||||||||||||
pub fn get(&self, key: K) -> Option<V> { | ||||||||||||
let htab = self.htab.exclusive(); | ||||||||||||
let (key_ptr, hash_value) = key!(key, htab); | ||||||||||||
|
||||||||||||
let entry = unsafe { | ||||||||||||
pg_sys::hash_search_with_hash_value( | ||||||||||||
Comment on lines
+170
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please document the preconditions for calling this function somewhere in this file, you can reference them in later |
||||||||||||
htab.htab, | ||||||||||||
key_ptr, | ||||||||||||
hash_value, | ||||||||||||
pg_sys::HASHACTION_HASH_FIND, | ||||||||||||
std::ptr::null_mut(), | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In particular, it is good to know why passing nullptr is okay here. |
||||||||||||
) | ||||||||||||
}; | ||||||||||||
|
||||||||||||
if entry.is_null() { | ||||||||||||
return None; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
} else { | ||||||||||||
let value_ptr = value_ptr!(entry); | ||||||||||||
let value = unsafe { std::ptr::read(value_ptr) }; | ||||||||||||
return Some(value.value); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+183
to
+185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason this returns |
||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Remove the value from the HashMap and return it. | ||||||||||||
pub fn remove(&self, key: K) -> Option<V> { | ||||||||||||
if let Some(value) = self.get(key) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably use let-else, like so:
Suggested change
|
||||||||||||
let mut htab = self.htab.exclusive(); | ||||||||||||
let (key_ptr, hash_value) = key!(key, htab); | ||||||||||||
|
||||||||||||
// Dangling pointer, don't touch it. | ||||||||||||
let _ = unsafe { | ||||||||||||
pg_sys::hash_search_with_hash_value( | ||||||||||||
htab.htab, | ||||||||||||
key_ptr, | ||||||||||||
hash_value, | ||||||||||||
pg_sys::HASHACTION_HASH_REMOVE, | ||||||||||||
std::ptr::null_mut(), | ||||||||||||
); | ||||||||||||
}; | ||||||||||||
|
||||||||||||
htab.elements -= 1; | ||||||||||||
return Some(value); | ||||||||||||
} else { | ||||||||||||
return None; | ||||||||||||
} | ||||||||||||
Comment on lines
+208
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if you do use let-else this should probably be updated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like this, but then it will want you to hit it with rustfmt.
Suggested change
|
||||||||||||
} | ||||||||||||
|
||||||||||||
/// Get the number of elements in the HashMap. | ||||||||||||
pub fn len(&self) -> usize { | ||||||||||||
let htab = self.htab.exclusive(); | ||||||||||||
htab.elements.try_into().unwrap() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl<K: Copy + Clone, V: Copy + Clone> PgSharedMemoryInitialization for PgHashMap<K, V> { | ||||||||||||
fn pg_init(&'static self) { | ||||||||||||
PgSharedMem::pg_init_locked(&self.htab); | ||||||||||||
} | ||||||||||||
|
||||||||||||
fn shmem_init(&'static self) { | ||||||||||||
PgSharedMem::shmem_init_locked(&self.htab); | ||||||||||||
let mut htab = self.htab.exclusive(); | ||||||||||||
|
||||||||||||
let mut hash_ctl = pg_sys::HASHCTL::default(); | ||||||||||||
hash_ctl.keysize = std::mem::size_of::<Key<K>>(); | ||||||||||||
hash_ctl.entrysize = std::mem::size_of::<Value<V>>(); | ||||||||||||
|
||||||||||||
let shm_name = | ||||||||||||
alloc::ffi::CString::new(Uuid::new_v4().to_string()).expect("CString::new() failed"); | ||||||||||||
|
||||||||||||
let htab_ptr = unsafe { | ||||||||||||
pg_sys::ShmemInitHash( | ||||||||||||
Comment on lines
+238
to
+239
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preconditions, postconditions, "why is this sound?", etc. |
||||||||||||
shm_name.into_raw(), | ||||||||||||
self.size.try_into().unwrap(), | ||||||||||||
self.size.try_into().unwrap(), | ||||||||||||
&mut hash_ctl, | ||||||||||||
(pg_sys::HASH_ELEM | pg_sys::HASH_BLOBS).try_into().unwrap(), | ||||||||||||
) | ||||||||||||
}; | ||||||||||||
|
||||||||||||
htab.htab = htab_ptr; | ||||||||||||
} | ||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A public error type shall be given a name that does not collide with the name of the Error trait.