Repository navigation
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
| /// The symbol memcached looks up when it loads this library with `-X`. | ||
| #[unsafe(no_mangle)] | ||
| pub extern "C" fn memcached_extensions_initialize( | ||
| _config: *const c_char, |
There was a problem hiding this comment.
memcached 쪽으로 패닉이 넘어가지 않게 최대한 막아야 하는 걸로 알고 있습니다.
지금은 린트로 패닉 자체가 발생하지 않도록 막고 있긴 한데, FFI 경계마다 catch_unwind를 두어 안전망을 하나 더 두는 건 어떨까요?
There was a problem hiding this comment.
수정되었습니다.
가독성을 높이기 위해 catch_unwind를 guard 함수로 래핑했습니다.
There was a problem hiding this comment.
panic이 발생하게 되는 경우 처리 흐름은 다음와 같습니다.
| 진입점 | 실패·패닉 시 반환 | 서버(core) 처리 |
|---|---|---|
memcached_extensions_initialize |
EXTENSION_FATAL |
load_extension 실패 → exit(EXIT_FAILURE)로 서버 시작 중단 |
accept |
true |
본문을 읽지 않고 같은 스레드에서 바로 execute 호출(아래 참고) |
execute |
false |
해당 연결만 닫음(conn_closing), 데몬과 다른 연결은 유지 |
accept에서 패닉이 났을 때
| 순서 | 동작 |
|---|---|
| 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.
accept에서 패닉 발생 시에 false 리턴해야 하지 않나요?
현재 extention에서 처리할 수 있는 명령인지를 확인하지 못했기 때문이며, 따라서 다른 extension에서 처리할 수 있는 지 확인하기 때문입니다.
There was a problem hiding this comment.
주요 패닉은 린트로 막고 있어서, 발생한다면 예상하지 못한 버그인 경우라고 생각합니다. 그때는 accept가 어디까지 처리했는지 믿을 수 없으니 연결을 끊는 게 안전해 보입니다.
명령 이름 비교에서는 패닉이 날 여지가 거의 없어서, 패닉이 난다면 이미 vop로 확인된 뒤일 가능성이 높습니다. 그래서 다른 extension이 처리하도록 false를 반환하는 경우는 크게 고려하지 않아도 될 것 같습니다.
오히려 false를 반환하면 코어가 ERROR unknown command를 보내고 연결을 유지한 채 다음 줄을 읽습니다. 앞으로 vop upsert, vop search처럼 본문이 있는 명령이 들어오면 본문이 다음 명령으로 해석되어 응답이 어긋나거나, 본문 내용이 다른 명령으로 실행될 수 있습니다.
accept에서 연결을 끊으려면 true로 받은 뒤 execute에서 false를 반환하는 방법뿐이라, 지금 방식이 맞다고 봅니다.
다만 SASL 권한 검사에서 막히면 execute가 호출되지 않아 ACCEPT_PANICKED가 남을 수 있으니, accept 시작 시 플래그를 초기화해두면 좋을 것 같습니다.
|
@ing-eoking |
|
scrub처럼 기능 자체는 엔진에 이미 구현되어 있고 확장은 프로토콜만 연결해 주는 경우라면, 코드가 짧고 단순해서 C로 구현하는 편이 더 낫다고 생각합니다. 다만 vector 기능은 엔진 처리만으로 끝나지 않는 비교적 무거운 기능입니다. HNSW 인덱스 관리, 검색을 처리하는 별도 스레드, 엔진 이벤트에 맞춘 인덱스 갱신, 메타데이터·필터 처리처럼 확장 쪽에 들어가야 하는 코드가 많습니다. 이런 상황에서 Rust는 다음과 같은 장점이 있습니다.
정리하면, 단순히 엔진 기능을 노출하는 확장은 C가, vector처럼 확장 쪽에 무거운 로직이 들어가는 기능은 Rust가 더 안전하고 유지보수하기 좋다고 판단해서 Rust를 선택했습니다. |
|
@zhy2on |
| extension_type_t_EXTENSION_ASCII_PROTOCOL, | ||
| }; | ||
|
|
||
| pub(crate) fn guard<T>(on_panic: impl FnOnce() -> T, f: impl FnOnce() -> T) -> T { |
There was a problem hiding this comment.
Rust 기본 패닉 처리기는 메시지를 stderr로 출력하는데,
memcached를 -d로 띄우면 -v가 없을 때 stderr가 /dev/null로 돌려져서 운영 환경에서는 패닉 위치와 메시지가 남지 않습니다.
초기화 시 std::panic::set_hook으로 패닉 메시지를 memcached logger로 남기면 좋을 것 같습니다.
이슈로 두고 추후에 적용해도 좋을 것 같습니다.
There was a problem hiding this comment.
추후에 logger 모듈을 추가하는 PR을 올릴 예정입니다.
set_hook 부분은 해당 PR에서 함께 처리될 것 같습니다.
| } | ||
|
|
||
| #[test] | ||
| fn a_panicked_accept_closes_the_connection() { |
There was a problem hiding this comment.
ACCEPT_PANICKED가 true일 때 false로 종료하는지 확인하는 테스트로 보입니다.
그런데 response_handler가 None이어도 false가 반환되어서, 지금은 ACCEPT_PANICKED 때문에 종료된 것인지 구분하기 어려울 것 같습니다.
None 대신 Some(record)를 넘기면 더 확실하게 확인할 수 있을 것 같습니다.
🔗 Related Issue
⌨️ What I did
memcached가
-X로 arcus-vector를 불러올 때vop명령을 받을 수 있도록 ASCII 프로토콜 확장을 등록합니다.아직 실제 명령은 구현하지 않았으며, 모든
vop명령에NOT_SUPPORTED로 응답합니다.src/lib.rscargo init예제 코드를 지우고, memcached가 호출하는 진입점memcached_extensions_initialize를 추가합니다.src/ascii.rs: EE의example_protocol.c와 같은 구조의 descriptor입니다.Summary by CodeRabbit
vop인 명령을 인식하며, 해당 명령에는 지원되지 않는다는 응답을 반환합니다.