Repository navigation
INTERNAL: Register the vop ASCII extension #6
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
Changes from all commits
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,200 @@ | ||
| use std::cell::Cell; | ||
| use std::os::raw::{c_char, c_int, c_void}; | ||
| use std::ptr; | ||
|
|
||
| use crate::guard; | ||
| use crate::memcached::ffi::{AUTHZ_NONE, EXTENSION_ASCII_PROTOCOL_DESCRIPTOR, token_t}; | ||
|
|
||
| const COMMAND: &[u8] = b"vop"; | ||
| const NOT_SUPPORTED: &[u8] = b"NOT_SUPPORTED\r\n"; | ||
|
|
||
| thread_local! { | ||
| /// Lets the `execute` the core calls right after a panicked `accept` close the connection. | ||
| static ACCEPT_PANICKED: Cell<bool> = const { Cell::new(false) }; | ||
| } | ||
|
|
||
| type ResponseHandler = | ||
| Option<unsafe extern "C" fn(cookie: *const c_void, nbytes: c_int, dta: *const c_char) -> bool>; | ||
|
|
||
| pub(crate) static mut DESCRIPTOR: EXTENSION_ASCII_PROTOCOL_DESCRIPTOR = | ||
| EXTENSION_ASCII_PROTOCOL_DESCRIPTOR { | ||
| get_name: Some(get_name), | ||
| get_auth_flag: Some(get_auth_flag), | ||
| accept: Some(accept), | ||
| execute: Some(execute), | ||
| abort: Some(abort), | ||
| cookie: ptr::null(), | ||
| next: ptr::null_mut(), | ||
| }; | ||
|
|
||
| extern "C" fn get_name(_cmd_cookie: *const c_void) -> *const c_char { | ||
| c"arcus_vector".as_ptr() | ||
| } | ||
|
|
||
| extern "C" fn get_auth_flag() -> u16 { | ||
| AUTHZ_NONE as u16 | ||
| } | ||
|
|
||
| /// # Safety | ||
| /// | ||
| /// Non-null `argv` must be a token with `length` readable bytes; non-null `ndata` must be writable. | ||
| unsafe extern "C" fn accept( | ||
| _cmd_cookie: *const c_void, | ||
| _cookie: *mut c_void, | ||
| argc: c_int, | ||
| argv: *mut token_t, | ||
| ndata: *mut usize, | ||
| _ptr: *mut *mut c_char, | ||
| ) -> bool { | ||
| // Clear a flag whose `execute` never ran (refused by SASL), or it closes another connection. | ||
| ACCEPT_PANICKED.set(false); | ||
| let claim_and_close = || { | ||
| if !ndata.is_null() { | ||
| // SAFETY: `ndata` is non-null, so the caller guarantees it is valid to write. | ||
| unsafe { *ndata = 0 }; | ||
| } | ||
| ACCEPT_PANICKED.set(true); | ||
| true | ||
| }; | ||
| guard(claim_and_close, || { | ||
| if argc < 1 || argv.is_null() { | ||
| return false; | ||
| } | ||
| // SAFETY: `argc >= 1` and `argv` is non-null, so it points to a valid token. | ||
| let first = unsafe { &*argv }; | ||
| if first.value.is_null() { | ||
| return false; | ||
| } | ||
| // SAFETY: `value` is non-null, so it points to `length` readable bytes. | ||
| let name = unsafe { std::slice::from_raw_parts(first.value.cast::<u8>(), first.length) }; | ||
| name == COMMAND | ||
| }) | ||
| } | ||
|
|
||
| /// # Safety | ||
| /// | ||
| /// `response_handler`, if present, must accept `cookie` and bytes that live for the call. | ||
| unsafe extern "C" fn execute( | ||
| _cmd_cookie: *const c_void, | ||
| cookie: *const c_void, | ||
| _argc: c_int, | ||
| _argv: *mut token_t, | ||
| response_handler: ResponseHandler, | ||
| ) -> bool { | ||
| guard( | ||
| || false, | ||
| || { | ||
| if ACCEPT_PANICKED.replace(false) { | ||
| return false; | ||
| } | ||
| let Some(respond) = response_handler else { | ||
| return false; | ||
| }; | ||
| // SAFETY: `respond` accepts `cookie`, and `NOT_SUPPORTED` is `'static`. | ||
| unsafe { | ||
| respond( | ||
| cookie, | ||
| NOT_SUPPORTED.len() as c_int, | ||
| NOT_SUPPORTED.as_ptr().cast(), | ||
| ) | ||
| } | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| extern "C" fn abort(_cmd_cookie: *const c_void, _cookie: *const c_void) {} | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn token(text: &str) -> token_t { | ||
| token_t { | ||
| value: text.as_ptr().cast_mut().cast(), | ||
| length: text.len(), | ||
| } | ||
| } | ||
|
|
||
| fn accepts(line: &[&str]) -> bool { | ||
| let mut tokens: Vec<token_t> = line.iter().map(|t| token(t)).collect(); | ||
| // SAFETY: `tokens` point into `line`, which outlives the call. | ||
| unsafe { | ||
| accept( | ||
| ptr::null(), | ||
| ptr::null_mut(), | ||
| line.len() as c_int, | ||
| tokens.as_mut_ptr(), | ||
| ptr::null_mut(), | ||
| ptr::null_mut(), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_panicked_accept_closes_the_connection() { | ||
|
Member
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.
Collaborator
Author
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. ์์ ๋์์ต๋๋ค.
Member
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. ํ์ธํ์ต๋๋ค. ์ด์ ์์ต๋๋ค. ๋จธ์งํด๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค. @jhpark816 |
||
| ACCEPT_PANICKED.set(true); | ||
| let mut reply: Vec<u8> = Vec::new(); | ||
| let mut tokens = vec![token("vop")]; | ||
| // SAFETY: `record` gets the unshared `reply` back as its cookie. | ||
| let ok = unsafe { | ||
| execute( | ||
| ptr::null(), | ||
| (&raw mut reply).cast(), | ||
| 1, | ||
| tokens.as_mut_ptr(), | ||
| Some(record), | ||
| ) | ||
| }; | ||
| assert!(!ok); | ||
| assert!(reply.is_empty()); | ||
| assert!(!ACCEPT_PANICKED.get()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn accept_clears_a_flag_left_behind() { | ||
| ACCEPT_PANICKED.set(true); | ||
| assert!(!accepts(&["get", "key"])); | ||
| assert!(!ACCEPT_PANICKED.get()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn accepts_only_vop_lines() { | ||
| assert!(accepts(&["vop", "create", "idx", "2", "0"])); | ||
| assert!(!accepts(&["get", "key"])); | ||
| assert!(!accepts(&["vopx"])); | ||
| assert!(!accepts(&[])); | ||
| } | ||
|
|
||
| /// # Safety | ||
| /// | ||
| /// `cookie` must be an unshared `Vec<u8>`, and `dta` must hold `nbytes` bytes. | ||
| unsafe extern "C" fn record(cookie: *const c_void, nbytes: c_int, dta: *const c_char) -> bool { | ||
| // SAFETY: the caller guarantees both, per `# Safety`. | ||
| let (reply, bytes) = unsafe { | ||
| ( | ||
| &mut *cookie.cast_mut().cast::<Vec<u8>>(), | ||
| std::slice::from_raw_parts(dta.cast::<u8>(), nbytes as usize), | ||
| ) | ||
| }; | ||
| reply.extend_from_slice(bytes); | ||
| true | ||
| } | ||
|
|
||
| #[test] | ||
| fn every_vop_line_is_not_supported() { | ||
| let mut reply: Vec<u8> = Vec::new(); | ||
| let mut tokens = vec![token("vop"), token("create")]; | ||
| // SAFETY: `record` gets the unshared `reply` back as its cookie. | ||
| let ok = unsafe { | ||
| execute( | ||
| ptr::null(), | ||
| (&raw mut reply).cast(), | ||
| 2, | ||
| tokens.as_mut_ptr(), | ||
| Some(record), | ||
| ) | ||
| }; | ||
| assert!(ok); | ||
| assert_eq!(reply, b"NOT_SUPPORTED\r\n"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,16 +1,72 @@ | ||||||||||||||||||||||||||
| //! arcus-vector: vector search as an extension to arcus-memcached. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| mod ascii; | ||||||||||||||||||||||||||
| mod memcached; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| pub fn add(left: u64, right: u64) -> u64 { | ||||||||||||||||||||||||||
| left + right | ||||||||||||||||||||||||||
| use std::os::raw::c_char; | ||||||||||||||||||||||||||
| use std::panic::{AssertUnwindSafe, catch_unwind}; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| use crate::memcached::ffi::{ | ||||||||||||||||||||||||||
| EXTENSION_ERROR_CODE, EXTENSION_ERROR_CODE_EXTENSION_FATAL, | ||||||||||||||||||||||||||
| EXTENSION_ERROR_CODE_EXTENSION_SUCCESS, GET_SERVER_API, | ||||||||||||||||||||||||||
| extension_type_t_EXTENSION_ASCII_PROTOCOL, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| pub(crate) fn guard<T>(on_panic: impl FnOnce() -> T, f: impl FnOnce() -> T) -> T { | ||||||||||||||||||||||||||
|
Member
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. Rust ๊ธฐ๋ณธ ํจ๋ ์ฒ๋ฆฌ๊ธฐ๋ ๋ฉ์์ง๋ฅผ stderr๋ก ์ถ๋ ฅํ๋๋ฐ, memcached๋ฅผ ์ด๊ธฐํ ์ ์ด์๋ก ๋๊ณ ์ถํ์ ์ ์ฉํด๋ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
Collaborator
Author
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. ์ถํ์ logger ๋ชจ๋์ ์ถ๊ฐํ๋ PR์ ์ฌ๋ฆด ์์ ์
๋๋ค. |
||||||||||||||||||||||||||
| catch_unwind(AssertUnwindSafe(f)).unwrap_or_else(|_| on_panic()) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| #[unsafe(no_mangle)] | ||||||||||||||||||||||||||
| pub extern "C" fn memcached_extensions_initialize( | ||||||||||||||||||||||||||
| _config: *const c_char, | ||||||||||||||||||||||||||
|
Member
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. memcached ์ชฝ์ผ๋ก ํจ๋์ด ๋์ด๊ฐ์ง ์๊ฒ ์ต๋ํ ๋ง์์ผ ํ๋ ๊ฑธ๋ก ์๊ณ ์์ต๋๋ค. ์ง๊ธ์ ๋ฆฐํธ๋ก ํจ๋ ์์ฒด๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๋ง๊ณ ์๊ธด ํ๋ฐ, FFI ๊ฒฝ๊ณ๋ง๋ค
Collaborator
Author
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. ์์ ๋์์ต๋๋ค. ๊ฐ๋ ์ฑ์ ๋์ด๊ธฐ ์ํด catch_unwind๋ฅผ guard ํจ์๋ก ๋ํํ์ต๋๋ค.
Collaborator
Author
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. panic์ด ๋ฐ์ํ๊ฒ ๋๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌ ํ๋ฆ์ ๋ค์์ ๊ฐ์ต๋๋ค.
|
||||||||||||||||||||||||||
| ์์ | ๋์ |
|---|---|
| 1 | *ndata = 0์ผ๋ก ๋๋๋ ค ์ฝ์ด์์ conn_nread๋ก ๊ฐ์ง ์๊ฒ ํจ |
| 2 | ์ค๋ ๋ ๋ก์ปฌ ํ๋๊ทธ ACCEPT_PANICKED ์ค์ |
| 3 | true ๋ฐํ โ ์ค์ ์ด ํ์ฅ์ด ๋ฐ์ ๊ฒ์ผ๋ก ์ฒ๋ฆฌ (ERROR unknown command๋ฅผ ๋ณด๋ด์ง ์์) |
| 4 | core๊ฐ ๊ฐ์ ์ค๋ ๋์์ ๊ณง๋ฐ๋ก execute ํธ์ถ |
| 5 | execute๊ฐ ํ๋๊ทธ๋ฅผ ๋ณด๊ณ false ๋ฐํ โ ์ฐ๊ฒฐ ์ข
๋ฃ, ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ ๋ณธ๋ฌธ์ ์ฐ๊ฒฐ๊ณผ ํจ๊ป ๋ฒ๋ ค์ง |
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.
accept์์ ํจ๋ ๋ฐ์ ์์ false ๋ฆฌํดํด์ผ ํ์ง ์๋์?
ํ์ฌ extention์์ ์ฒ๋ฆฌํ ์ ์๋ ๋ช
๋ น์ธ์ง๋ฅผ ํ์ธํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ด๋ฉฐ, ๋ฐ๋ผ์ ๋ค๋ฅธ extension์์ ์ฒ๋ฆฌํ ์ ์๋ ์ง ํ์ธํ๊ธฐ ๋๋ฌธ์
๋๋ค.
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.
์ฃผ์ ํจ๋์ ๋ฆฐํธ๋ก ๋ง๊ณ ์์ด์, ๋ฐ์ํ๋ค๋ฉด ์์ํ์ง ๋ชปํ ๋ฒ๊ทธ์ธ ๊ฒฝ์ฐ๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋๋ accept๊ฐ ์ด๋๊น์ง ์ฒ๋ฆฌํ๋์ง ๋ฏฟ์ ์ ์์ผ๋ ์ฐ๊ฒฐ์ ๋๋ ๊ฒ ์์ ํด ๋ณด์
๋๋ค.
๋ช
๋ น ์ด๋ฆ ๋น๊ต์์๋ ํจ๋์ด ๋ ์ฌ์ง๊ฐ ๊ฑฐ์ ์์ด์, ํจ๋์ด ๋๋ค๋ฉด ์ด๋ฏธ vop๋ก ํ์ธ๋ ๋ค์ผ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๊ทธ๋์ ๋ค๋ฅธ extension์ด ์ฒ๋ฆฌํ๋๋ก false๋ฅผ ๋ฐํํ๋ ๊ฒฝ์ฐ๋ ํฌ๊ฒ ๊ณ ๋ คํ์ง ์์๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์คํ๋ ค false๋ฅผ ๋ฐํํ๋ฉด ์ฝ์ด๊ฐ ERROR unknown command๋ฅผ ๋ณด๋ด๊ณ ์ฐ๊ฒฐ์ ์ ์งํ ์ฑ ๋ค์ ์ค์ ์ฝ์ต๋๋ค. ์์ผ๋ก vop upsert, vop search์ฒ๋ผ ๋ณธ๋ฌธ์ด ์๋ ๋ช
๋ น์ด ๋ค์ด์ค๋ฉด ๋ณธ๋ฌธ์ด ๋ค์ ๋ช
๋ น์ผ๋ก ํด์๋์ด ์๋ต์ด ์ด๊ธ๋๊ฑฐ๋, ๋ณธ๋ฌธ ๋ด์ฉ์ด ๋ค๋ฅธ ๋ช
๋ น์ผ๋ก ์คํ๋ ์ ์์ต๋๋ค.
accept์์ ์ฐ๊ฒฐ์ ๋์ผ๋ ค๋ฉด true๋ก ๋ฐ์ ๋ค execute์์ false๋ฅผ ๋ฐํํ๋ ๋ฐฉ๋ฒ๋ฟ์ด๋ผ, ์ง๊ธ ๋ฐฉ์์ด ๋ง๋ค๊ณ ๋ด
๋๋ค.
๋ค๋ง SASL ๊ถํ ๊ฒ์ฌ์์ ๋งํ๋ฉด execute๊ฐ ํธ์ถ๋์ง ์์ ACCEPT_PANICKED๊ฐ ๋จ์ ์ ์์ผ๋, accept ์์ ์ ํ๋๊ทธ๋ฅผ ์ด๊ธฐํํด๋๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
Uh oh!
There was an error while loading. Please reload this page.