feat(infra): Docker-compose configuration and environment-specific security guards for Beta deployment to Test environment #56
@@ -59,7 +59,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- qdrant_test_data:/qdrant/storage
|
- qdrant_test_data:/qdrant/storage
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/6333'"]
|
test: ["CMD-SHELL", "curl -sf http://localhost:6333/healthz || exit 1"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
_selfReference = DotNetObjectReference.Create(this);
|
||||||
await SyncService.InitializeAsync();
|
await SyncService.InitializeAsync();
|
||||||
_isInteractive = true;
|
_isInteractive = true;
|
||||||
if (ViewModel != null)
|
if (ViewModel != null)
|
||||||
@@ -173,11 +174,13 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var module = await EnsureViewportModuleAsync();
|
var module = await EnsureViewportModuleAsync();
|
||||||
_selfReference = DotNetObjectReference.Create(this);
|
|
||||||
var isMobileViewport = await module.InvokeAsync<bool>("isMobileViewport");
|
var isMobileViewport = await module.InvokeAsync<bool>("isMobileViewport");
|
||||||
await OnViewportChanged(isMobileViewport);
|
await OnViewportChanged(isMobileViewport);
|
||||||
|
if (_selfReference != null)
|
||||||
|
{
|
||||||
await module.InvokeVoidAsync("registerViewportObserver", _selfReference);
|
await module.InvokeVoidAsync("registerViewportObserver", _selfReference);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Failed to initialize viewport detection in ReaderCanvas.");
|
Logger.LogWarning(ex, "Failed to initialize viewport detection in ReaderCanvas.");
|
||||||
@@ -199,7 +202,10 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js");
|
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js");
|
||||||
await module.InvokeVoidAsync("initSelectionListener", DotNetObjectReference.Create(this), _containerRef);
|
if (_selfReference != null)
|
||||||
|
{
|
||||||
|
await module.InvokeVoidAsync("initSelectionListener", _selfReference, _containerRef);
|
||||||
|
mjasin marked this conversation as resolved
Outdated
|
|||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -214,8 +220,11 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/readerObserver.js");
|
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/readerObserver.js");
|
||||||
|
mjasin marked this conversation as resolved
Outdated
Antigravity
commented
🟡 Design — Still unresolved. Untracked
Create a dedicated 🟡 **Design — Still unresolved. Untracked `DotNetObjectReference` in `InitializeObserverAsync`.**
`DotNetObjectReference.Create(this)` is called twice inside `InitializeObserverAsync` (once for `initObserver` and once already implicitly for `initScrollListener` via the `_scrollListenerReference` returned value). The reference passed to `initObserver` is not stored in a field and therefore cannot be disposed, creating a GC-root memory leak from the JS side.
Create a dedicated `_observerSelfReference` field:
```csharp
// Field
private DotNetObjectReference<ReaderCanvas>? _observerSelfReference;
// In InitializeObserverAsync:
_observerSelfReference = DotNetObjectReference.Create(this);
await module.InvokeVoidAsync("initObserver", _observerSelfReference, ".reader-flow-container", ".block-wrapper");
// In DisposeAsync:
_observerSelfReference?.Dispose();
```
|
|||||||
await module.InvokeVoidAsync("initObserver", DotNetObjectReference.Create(this), ".reader-flow-container", ".block-wrapper");
|
if (_selfReference != null)
|
||||||
_scrollListenerReference = await module.InvokeAsync<IJSObjectReference>("initScrollListener", DotNetObjectReference.Create(this), ".reader-flow-container");
|
{
|
||||||
|
await module.InvokeVoidAsync("initObserver", _selfReference, ".reader-flow-container", ".block-wrapper");
|
||||||
|
_scrollListenerReference = await module.InvokeAsync<IJSObjectReference>("initScrollListener", _selfReference, ".reader-flow-container");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -454,7 +463,10 @@
|
|||||||
await _scrollListenerReference.DisposeAsync();
|
await _scrollListenerReference.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Teardown of scroll listener reference failed in ReaderCanvas disposal.");
|
||||||
|
}
|
||||||
|
|
||||||
_selfReference?.Dispose();
|
_selfReference?.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user
🟡 Design —
DotNetObjectReferencecreated insideInitializeObserverAsyncwithout being stored or disposedIn
InitializeObserverAsync, a newDotNetObjectReference.Create(this)is passed toinitObserver. This reference is not assigned to a field, so it cannot be disposed inDisposeAsync. The unmanaged JS side will hold a reference to the GC'd .NET object indefinitely, which is a memory leak.The same pattern was correctly fixed for
_selfReference(for the viewport observer). Apply the same treatment here — create a second_observerSelfReferencefield, assign it, and dispose it inDisposeAsync: