[AI Task] [Tizen.Multimedia.Recorder] Make GetSupportedVideoResolutions cache thread-safe via Lazy<T>#7712
[AI Task] [Tizen.Multimedia.Recorder] Make GetSupportedVideoResolutions cache thread-safe via Lazy<T>#7712JoonghyunCho wants to merge 2 commits into
Conversation
…hread-safe via Lazy<T> Convert the inline-assigned static _frontResolutions/_rearResolutions caches to Lazy<IEnumerable<Size>>. The previous read/null-check/assign pattern was non-atomic, so two concurrent callers could each run LoadVideoResolutions and open a native Camera handle twice. Lazy<T>'s default ExecutionAndPublication mode guarantees a single execution. Public API signature and success-path behavior are unchanged. Fixes #7641 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the lazy initialization of camera resolutions in VideoRecorder.Capabilities.cs to use Lazy<IEnumerable> and simplifies the retrieval logic with a switch statement. However, using Lazy can permanently cache transient exceptions thrown during camera initialization (e.g., if the camera is temporarily busy). It is recommended to use a thread-safe, lock-based initialization pattern instead, ensuring that transient failures do not permanently poison the cache and subsequent attempts can retry the initialization.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| private static readonly Lazy<IEnumerable<Size>> _frontResolutions = | ||
| new Lazy<IEnumerable<Size>>(() => LoadVideoResolutions(CameraDevice.Front)); | ||
|
|
||
| private static readonly Lazy<IEnumerable<Size>> _rearResolutions = | ||
| new Lazy<IEnumerable<Size>>(() => LoadVideoResolutions(CameraDevice.Rear)); |
There was a problem hiding this comment.
Using Lazy<T> with the default constructor (which uses LazyThreadSafetyMode.ExecutionAndPublication) will cache any exception thrown during initialization.
Since LoadVideoResolutions instantiates a Camera object, it can throw transient exceptions (e.g., if the camera is temporarily busy or in use by another process). If an exception is thrown, the Lazy<T> instance will permanently cache that exception, causing all subsequent calls to GetSupportedVideoResolutions to fail even if the camera becomes available later.
To prevent transient failures from permanently poisoning the cache, we should use a lock-based initialization pattern instead of Lazy<T>.
private static IEnumerable<Size> _frontResolutions;
private static readonly object _frontLock = new object();
private static IEnumerable<Size> _rearResolutions;
private static readonly object _rearLock = new object();| switch (device) | ||
| { | ||
| return _rearResolutions ?? (_rearResolutions = LoadVideoResolutions(CameraDevice.Rear)); | ||
| case CameraDevice.Front: | ||
| return _frontResolutions.Value; | ||
| case CameraDevice.Rear: | ||
| return _rearResolutions.Value; | ||
| default: | ||
| Debug.Fail($"No cache for {device}."); | ||
| return LoadVideoResolutions(device); | ||
| } |
There was a problem hiding this comment.
Update the resolution retrieval logic to use a simple lock-based initialization. This ensures thread safety and guarantees that if LoadVideoResolutions throws a transient exception, the fields remain null so that subsequent calls can retry the initialization.
switch (device)
{
case CameraDevice.Front:
lock (_frontLock)
{
return _frontResolutions ?? (_frontResolutions = LoadVideoResolutions(CameraDevice.Front));
}
case CameraDevice.Rear:
lock (_rearLock)
{
return _rearResolutions ?? (_rearResolutions = LoadVideoResolutions(CameraDevice.Rear));
}
default:
Debug.Fail($"No cache for {device}.");
return LoadVideoResolutions(device);
}|
🤖 [AI Review] Reviewed — no findings. Scope checked:
No 🔴 critical issues, no 🟡 suggestions to flag. Automated review — final merge decision rests with human reviewers. |
Replace Lazy<T> resolution cache with lock-based lazy initialization so a transient failure in LoadVideoResolutions (e.g. camera busy) is not permanently cached and subsequent calls can retry. Applied-Human-Comments: 3448300022,3448300024
|
🤖 [AI Review] |
Summary
VideoRecorder.GetSupportedVideoResolutions(CameraDevice)cached its results in two static fields (_frontResolutions/_rearResolutions) using a non-atomic read → null-check → assign (?? (x = ...)) pattern. With no synchronization, two concurrent callers could each runLoadVideoResolutions, which opens a nativeCamerahandle. Tizen camera resources are single-owner, so the second open can fail withNotSupportedException/InvalidOperationException, and the first caller's result may be lost (overwritten cache write).This change converts both caches to
Lazy<IEnumerable<Size>>, whose defaultLazyThreadSafetyMode.ExecutionAndPublicationguarantees the factory runs exactly once even under concurrent access. This also aligns with the existing convention in the siblingRecorder.Capabilities.cs, which already usesLazy<Capabilities>.Changes
src/Tizen.Multimedia.Recorder/Recorder/VideoRecorder.Capabilities.cs_frontResolutions/_rearResolutions:IEnumerable<Size>→static readonly Lazy<IEnumerable<Size>>initialized with aLoadVideoResolutions(...)factory.GetSupportedVideoResolutions: return_frontResolutions.Value/_rearResolutions.Valuevia aswitchoverCameraDevice; thedefaultarm keeps the originalDebug.Fail+ direct-load fallback.Mode
Refactoring
Verification
dotnet build Tizen.Multimedia.Recorder— 0 errors; pre-existing warnings only)Fixes #7641