-
Notifications
You must be signed in to change notification settings - Fork 9
[DO NOT MERGE] fix: bypass JVM proxy for Kubernetes API client connections #341
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 |
|---|---|---|
|
|
@@ -14,6 +14,8 @@ package com.redhat.devtools.gateway.kubeconfig | |
| import kotlinx.coroutines.* | ||
| import java.nio.file.* | ||
| import java.util.concurrent.ConcurrentHashMap | ||
| import java.util.concurrent.TimeUnit | ||
| import java.util.concurrent.locks.ReentrantLock | ||
| import kotlin.io.path.exists | ||
| import kotlin.io.path.isRegularFile | ||
|
|
||
|
|
@@ -23,80 +25,144 @@ class FileWatcher( | |
| private val watchService: WatchService = FileSystems.getDefault().newWatchService() | ||
| ) { | ||
| private var onFileChanged: ((Path) -> Unit)? = null | ||
| private val registeredKeys = ConcurrentHashMap<WatchKey, Path>() | ||
| private val registeredDirectories = ConcurrentHashMap<WatchKey, Path>() | ||
| private val monitoredFiles = ConcurrentHashMap.newKeySet<Path>() | ||
| private val notifyLock = ReentrantLock() | ||
| private var watchJob: Job? = null | ||
|
|
||
| fun start() { | ||
| this.watchJob = scope.launch(dispatcher) { | ||
| try { | ||
| while (isActive) { | ||
| val key = watchService.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS) | ||
| if (key == null) { | ||
| @Suppress("ConvertLongToDuration") | ||
| delay(100) | ||
| continue | ||
| } | ||
| val dir = registeredKeys[key] ?: continue | ||
| pollEvents(key, dir) | ||
| key.reset() | ||
| pollDirectoryEvents() | ||
| } | ||
| } | ||
|
|
||
| private suspend fun CoroutineScope.pollDirectoryEvents() { | ||
| try { | ||
| while (isActive) { | ||
| val key = watchService.poll(100, TimeUnit.MILLISECONDS) | ||
| if (key == null) { | ||
| @Suppress("ConvertLongToDuration") | ||
| delay(100) | ||
| continue | ||
| } | ||
| } catch (e: ClosedWatchServiceException) { | ||
| // Watch service was closed, exit gracefully | ||
| val dir = registeredDirectories[key] ?: continue | ||
| handleDirectoryEvent(key, dir) | ||
| key.reset() | ||
| } | ||
| } catch (_: ClosedWatchServiceException) { | ||
| // Watch service was closed, exit gracefully | ||
| } | ||
| } | ||
|
|
||
| fun stop() { | ||
| watchJob?.cancel() | ||
| watchJob = null | ||
| registeredDirectories.keys.forEach { it.cancel() } | ||
| registeredDirectories.clear() | ||
| monitoredFiles.clear() | ||
| try { | ||
| watchService.close() | ||
| } catch (e: Exception) { | ||
| } catch (_: Exception) { | ||
| // Ignore exceptions when closing | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Registers [path] for watching and invokes the [onFileChanged] callback if this is | ||
| * a new file that was not previously monitored. For non-existent files, the callback | ||
| * is invoked so listeners can track them for a later CREATE event. If [path] is | ||
| * already in the monitored set, no callback is issued — this avoids redundant refreshes. | ||
| * | ||
| * @param path the file to monitor; may be non-existent (e.g. a kubeconfig file expected | ||
| * to appear later). If [path.parent] is null, the path will not be registered. | ||
| * @return this instance for chaining. | ||
| */ | ||
| fun addFile(path: Path): FileWatcher { | ||
| if (!path.exists() | ||
| || !path.isRegularFile()) { | ||
| return this | ||
| } | ||
| val parentDir = path.parent | ||
| if (parentDir != null | ||
| && !monitoredFiles.contains(path)) { | ||
| val watchKey = parentDir.register(watchService, | ||
| StandardWatchEventKinds.ENTRY_CREATE, | ||
| StandardWatchEventKinds.ENTRY_MODIFY, | ||
| StandardWatchEventKinds.ENTRY_DELETE | ||
| ) | ||
| registeredKeys[watchKey] = parentDir | ||
| registerDirectory(parentDir) | ||
| monitoredFiles.add(path) | ||
| onFileChanged?.invoke(path) | ||
| invokeOnFileChanged(path) | ||
| } | ||
| return this | ||
| } | ||
|
Comment on lines
80
to
89
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Unhandled
🛡️ Proposed fix private fun registerDirectory(directory: Path) {
if (registeredDirectories.values.any { it == directory }) {
return
}
- val watchKey = directory.register(
- watchService,
- StandardWatchEventKinds.ENTRY_CREATE,
- StandardWatchEventKinds.ENTRY_MODIFY,
- StandardWatchEventKinds.ENTRY_DELETE
- )
- registeredDirectories[watchKey] = directory
+ try {
+ val watchKey = directory.register(
+ watchService,
+ StandardWatchEventKinds.ENTRY_CREATE,
+ StandardWatchEventKinds.ENTRY_MODIFY,
+ StandardWatchEventKinds.ENTRY_DELETE
+ )
+ registeredDirectories[watchKey] = directory
+ } catch (e: IOException) {
+ // Parent directory doesn't exist yet; the file stays in monitoredFiles
+ // but won't be watched until a later addFile()/updateMonitoredPaths() retries.
+ }
}Also applies to: 116-127 🤖 Prompt for AI Agents |
||
|
|
||
| fun removeFile(path: Path): FileWatcher { | ||
| monitoredFiles.remove(path) | ||
| if (!monitoredFiles.remove(path)) { | ||
| return this | ||
| } | ||
| val parentDir = path.parent ?: return this | ||
| if (monitoredFiles.none { it.parent == parentDir }) { | ||
| unregisterDirectory(parentDir) | ||
| } | ||
| return this | ||
| } | ||
|
|
||
| fun getMonitoredFiles(): Set<Path> = monitoredFiles | ||
|
|
||
| /** Parent directories that currently have an active [WatchKey]. For tests. */ | ||
| internal fun getWatchedDirectories(): Set<Path> = registeredDirectories.values.toSet() | ||
|
|
||
| fun onFileChanged(action: ((Path) -> Unit)?) { | ||
| this.onFileChanged = action | ||
| notifyLock.lock() | ||
| try { | ||
| this.onFileChanged = action | ||
| } finally { | ||
| notifyLock.unlock() | ||
| } | ||
| } | ||
|
|
||
| private fun pollEvents(key: WatchKey, dir: Path) { | ||
| private fun registerDirectory(directory: Path) { | ||
| if (registeredDirectories.values.any { it == directory }) { | ||
| return | ||
| } | ||
| val watchKey = directory.register( | ||
| watchService, | ||
| StandardWatchEventKinds.ENTRY_CREATE, | ||
| StandardWatchEventKinds.ENTRY_MODIFY, | ||
| StandardWatchEventKinds.ENTRY_DELETE | ||
| ) | ||
| registeredDirectories[watchKey] = directory | ||
| } | ||
|
|
||
| private fun unregisterDirectory(directory: Path) { | ||
| val keys = registeredDirectories.filterValues { it == directory }.keys | ||
| keys.forEach { key -> | ||
| registeredDirectories.remove(key) | ||
| key.cancel() | ||
| } | ||
| } | ||
|
|
||
| private fun handleDirectoryEvent(key: WatchKey, dir: Path) { | ||
| for (event in key.pollEvents()) { | ||
| val relativePath = event.context() as? Path ?: continue | ||
| val changedFile = dir.resolve(relativePath) | ||
| val kind = event.kind() | ||
|
|
||
| if (monitoredFiles.contains(changedFile) | ||
| && event.kind() != StandardWatchEventKinds.OVERFLOW | ||
| if (!monitoredFiles.contains(changedFile) | ||
| || kind == StandardWatchEventKinds.OVERFLOW | ||
| ) { | ||
| onFileChanged?.invoke(changedFile) | ||
| continue | ||
| } | ||
|
|
||
| // DELETE: file is already gone — still notify so callers can refresh. | ||
| // CREATE/MODIFY: only notify for an existing regular file. | ||
| val shouldNotify = kind == StandardWatchEventKinds.ENTRY_DELETE | ||
| || (changedFile.exists() && changedFile.isRegularFile()) | ||
|
|
||
| if (shouldNotify) { | ||
| invokeOnFileChanged(changedFile) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun invokeOnFileChanged(path: Path) { | ||
| notifyLock.lock() | ||
| try { | ||
| onFileChanged?.invoke(path) | ||
| } finally { | ||
| notifyLock.unlock() | ||
| } | ||
| } | ||
| } | ||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve or migrate the UI-test Gradle task.
.github/workflows/run-ui-tests.ymlstill invokesrunIdeForUiTestson Linux, Windows, and macOS. Removing that task causes every UI-test job to fail before the IDE starts. Update the workflow atomically or retain a compatibility task.🤖 Prompt for AI Agents