Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion bazel-jdt-bridge/crates/bazel-cache/src/redb_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,48 @@ pub enum CacheError {
IoError(#[from] std::io::Error),
}

fn is_lock_error(err: &redb::DatabaseError) -> bool {
matches!(err, redb::DatabaseError::DatabaseAlreadyOpen)
}

impl BazelCache {
/// Open or create the cache database
/// Open or create the cache database.
///
/// Uses a three-stage strategy for lock conflicts:
/// 1. Try to open normally
/// 2. If locked, sleep 500ms and retry (covers transient cross-process locks)
/// 3. If still locked, delete the .redb file and create a fresh database
pub fn open(cache_dir: &Path) -> Result<Self, CacheError> {
std::fs::create_dir_all(cache_dir)?;
let db_path = cache_dir.join("bazel-jdt-cache.redb");

// Stage 1: first attempt
match Database::create(&db_path) {
Ok(db) => return Ok(Self { db }),
Err(ref e) if is_lock_error(e) => {
log::warn!(
"Cache database locked, retrying in 500ms: {}",
db_path.display()
);
}
Err(e) => return Err(CacheError::DatabaseError(e)),
}

// Stage 2: retry after 500ms
std::thread::sleep(std::time::Duration::from_millis(500));
match Database::create(&db_path) {
Ok(db) => return Ok(Self { db }),
Err(ref e) if is_lock_error(e) => {
log::warn!(
"Cache database still locked after retry, recreating: {}",
db_path.display()
);
}
Err(e) => return Err(CacheError::DatabaseError(e)),
}

// Stage 3: delete and recreate
let _ = std::fs::remove_file(&db_path);
let db = Database::create(&db_path)?;
Ok(Self { db })
}
Expand Down
73 changes: 48 additions & 25 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/jni_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,31 @@ pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeInitialize(
}
};

let state = match BazelJdtState::new(
std::path::PathBuf::from(&workspace),
&bazel,
std::path::Path::new(&cache),
) {
let cache_path = std::path::PathBuf::from(&cache);

// Clean up any orphaned state using the same cache_dir (e.g., from a
// ClassLoader replacement that skipped nativeShutdown).
{
let mut reg = registry().lock().unwrap_or_else(|e| e.into_inner());
let orphan_keys: Vec<u64> = reg
.iter()
.filter(|(_, s)| s.cache_dir == cache_path)
.map(|(k, _)| *k)
.collect();
for key in orphan_keys {
if let Some(mut orphan) = reg.remove(&key) {
log::warn!(
"Cleaning up orphaned native state (handle={}) for cache_dir {:?}",
key,
cache_path
);
shutdown_state(&mut orphan);
}
}
}

let state = match BazelJdtState::new(std::path::PathBuf::from(&workspace), &bazel, &cache_path)
{
Ok(s) => s,
Err(e) => {
let _ = env.throw_new(
Expand Down Expand Up @@ -178,26 +198,7 @@ pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeInitialize(
key as jlong
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeShutdown(
_env: JNIEnv,
_class: JClass,
handle: jlong,
) {
if handle <= 0 {
return;
}
let key = handle as u64;

let mut state_box = {
let mut reg = registry().lock().unwrap_or_else(|e| e.into_inner());
match reg.remove(&key) {
Some(b) => b,
None => return,
}
};

let state = &mut *state_box;
fn shutdown_state(state: &mut BazelJdtState) {
state.signal_shutdown();
state.set_sync_state(SyncState::Dead);

Expand Down Expand Up @@ -227,6 +228,28 @@ pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeShutdown(
}
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeShutdown(
_env: JNIEnv,
_class: JClass,
handle: jlong,
) {
if handle <= 0 {
return;
}
let key = handle as u64;

let mut state_box = {
let mut reg = registry().lock().unwrap_or_else(|e| e.into_inner());
match reg.remove(&key) {
Some(b) => b,
None => return,
}
};

shutdown_state(&mut state_box);
}

fn make_watcher_callback(
registry_key: u64,
) -> Box<dyn Fn(Vec<std::path::PathBuf>) + Send + 'static> {
Expand Down
2 changes: 2 additions & 0 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum SyncState {
/// Central state for the Bazel JDT bridge
pub struct BazelJdtState {
pub cache: BazelCache,
pub cache_dir: PathBuf,
pub graph: Mutex<DependencyGraph>,
pub parser: BuildFileParser,
pub invoker: BazelInvoker,
Expand Down Expand Up @@ -75,6 +76,7 @@ impl BazelJdtState {

Ok(Self {
cache,
cache_dir: cache_dir.to_path_buf(),
graph: Mutex::new(graph),
parser,
invoker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ public void stop(BundleContext context) throws Exception {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(invisibleProjectListener);
invisibleProjectListener = null;
}
BazelBridge.getInstance().shutdown();
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Bazel JDT Bridge bundle stopping (bridge survives for container recovery)"));
"Bazel JDT Bridge bundle stopped (classpath recovery uses disk cache on next restart)"));
}

private void checkForInvisibleProjects(IResourceChangeEvent event) {
Expand Down
4 changes: 2 additions & 2 deletions bazel-jdt-bridge/vscode-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading