camera: keep devicesInUse aligned across flip + create failure
Two leak paths surfaced after a flip-then-record-then-pop session left the front camera claim stranded: 1. setDescription swapped instance.device without telling the plugin — devicesInUse still held the original cameraId. After dispose, releaseClaim only removed the *current* id, leaving the original stuck. Next push of the page hit device_busy on the original cam. Fix: setDescription handler now does a contention check, inserts the new id and drops the old (or rolls back on swap failure). 2. create's catch path called releaseClaim(for: instance), but if configureSession threw before instance.device was set, instance.currentCameraId is nil — and the cameraId we inserted on line above leaked. Fix: drop the known cameraId + audio claim explicitly in the catch.
This commit is contained in:
@@ -109,10 +109,29 @@ public class CameraPlugin: NSObject, NativePlugin, FlutterStreamHandler {
|
||||
return
|
||||
}
|
||||
withInstance(args, result: result) { instance in
|
||||
// Contention check against other instances. The instance's
|
||||
// current cameraId is held by us in `devicesInUse`; only a
|
||||
// foreign holder should block. (A no-op flip — same id —
|
||||
// also passes.)
|
||||
let oldId = instance.currentCameraId
|
||||
if oldId != cameraId, self.devicesInUse.contains(cameraId) {
|
||||
result(FlutterError(
|
||||
code: "device_busy",
|
||||
message: cameraId,
|
||||
details: nil
|
||||
))
|
||||
return
|
||||
}
|
||||
// Tentatively claim the new id before the async swap so a
|
||||
// concurrent create can't race us. Roll back on failure.
|
||||
self.devicesInUse.insert(cameraId)
|
||||
instance.sessionQueueAsync {
|
||||
do {
|
||||
let size = try instance.setDescription(cameraId: cameraId)
|
||||
DispatchQueue.main.async {
|
||||
if let oldId, oldId != cameraId {
|
||||
self.devicesInUse.remove(oldId)
|
||||
}
|
||||
result([
|
||||
"previewSize": [
|
||||
"width": size.width,
|
||||
@@ -122,6 +141,9 @@ public class CameraPlugin: NSObject, NativePlugin, FlutterStreamHandler {
|
||||
}
|
||||
} catch let error as NSError {
|
||||
DispatchQueue.main.async {
|
||||
if oldId != cameraId {
|
||||
self.devicesInUse.remove(cameraId)
|
||||
}
|
||||
result(FlutterError(
|
||||
code: "init_failed",
|
||||
message: error.localizedDescription,
|
||||
@@ -326,7 +348,13 @@ public class CameraPlugin: NSObject, NativePlugin, FlutterStreamHandler {
|
||||
}
|
||||
} catch let error as NSError {
|
||||
DispatchQueue.main.async {
|
||||
self.releaseClaim(for: instance)
|
||||
// Can't rely on `releaseClaim(for: instance)` here —
|
||||
// if `configureSession` threw before `instance.device`
|
||||
// was set, `instance.currentCameraId` is nil and the
|
||||
// claim we inserted on line above would leak. Drop the
|
||||
// ids we know we inserted explicitly.
|
||||
self.devicesInUse.remove(cameraId)
|
||||
if enableAudio { self.audioInUse = false }
|
||||
self.instances.removeValue(forKey: handle)
|
||||
result(FlutterError(
|
||||
code: "init_failed",
|
||||
|
||||
Reference in New Issue
Block a user