From 3a9f09a6eb6748eb9bdef6a9d0d40b813cd70350 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Mon, 12 Oct 2026 00:36:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94:=20dispose=20=EB=92=A4=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=94=EA=B8=B0=EA=B0=80=20=EB=81=9D=EB=82=98=EB=8F=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=AC=EC=A7=80=20=EC=95=8A=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 버그 (관리자 전체 시험에서 발견): 앱을 끄거나 화면을 닫는 중에 맞추기가 끝나면 used after disposed 오류 - 시험은 정해진 4초가 아니라 맞추기가 끝날 때까지 기다리고, 끝난 뒤 지움 (흔들림) Co-Authored-By: Claude Opus 5.5 --- lib/app/live_sync.dart | 10 ++++++++++ test/monitor_test.dart | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/app/live_sync.dart b/lib/app/live_sync.dart index 4847e62..bbd6b65 100644 --- a/lib/app/live_sync.dart +++ b/lib/app/live_sync.dart @@ -94,6 +94,7 @@ class LiveSync extends ChangeNotifier { @override void dispose() { + _disposed = true; c.removeListener(_apply); _clock?.cancel(); _stopAll(); @@ -101,6 +102,15 @@ class LiveSync extends ChangeNotifier { super.dispose(); } + /// 맞추기 (rsync · robocopy 프로세스) 는 dispose 뒤에도 끝까지 돌 수 있다 - 그때 끝나며 알리지 않게 + /// (앱을 끄거나 화면을 닫는 중에 맞추기가 끝나면 "used after being disposed" 오류가 났다) + bool _disposed = false; + + @override + void notifyListeners() { + if (!_disposed) super.notifyListeners(); + } + /// 지금 동작 시간인지 (일정이 없으면 늘) static bool activeNow(LiveSyncPair x, [DateTime? now]) => scheduleActive(x.schedule, now ?? DateTime.now()); diff --git a/test/monitor_test.dart b/test/monitor_test.dart index 29431b5..83500b9 100644 --- a/test/monitor_test.dart +++ b/test/monitor_test.dart @@ -207,22 +207,50 @@ void main() { ..liveSyncPairs = [pa, pb] ..liveSyncOnStart = 'off'; final live = LiveSync(c)..start(hold: c.settings.liveSyncOnStart != 'auto'); - addTearDown(live.dispose); + // 맞추기 (rsync 프로세스) 가 끝난 뒤 닫고 지운다 - 도는 중에 지우면 "다른 프로세스가 사용 중" (전체 시험이 무거울 때) + addTearDown(() async { + for (var i = 0; i < 200 && live.anyRunning; i++) { + await Future.delayed(const Duration(milliseconds: 100)); + } + live.dispose(); + }); + // 모아서 3초 뒤 맞춘다 - 바쁠 때는 늦으므로 정해진 4초가 아니라 될 때까지 (최대 30초) + Future until(bool Function() done) async { + for (var i = 0; i < 120 && !done(); i++) { + await Future.delayed(const Duration(milliseconds: 250)); + } + } + expect(live.watching, isEmpty); await Future.delayed(const Duration(seconds: 4)); expect(Directory(pa.target).existsSync(), isFalse); // 시작 안 함 live.runOnly([pa]); expect(live.watching.map((x) => x.source), [a.path]); expect(live.isPaused(pb), isTrue); - await Future.delayed(const Duration(seconds: 4)); + await until(() => File(p.join(pa.target, '1.txt')).existsSync() && !live.anyRunning); expect(File(p.join(pa.target, '1.txt')).existsSync(), isTrue); expect(Directory(pb.target).existsSync(), isFalse); live.resumeAll(); - await Future.delayed(const Duration(seconds: 4)); + await until(() => File(p.join(pb.target, '2.txt')).existsSync() && !live.anyRunning); expect(File(p.join(pb.target, '2.txt')).existsSync(), isTrue); live.pause(pa); expect(live.watching.map((x) => x.source), [b.path]); }); + + test('dispose 뒤에 맞추기가 끝나도 알리지 않는다 (앱을 끄는 중 - used after disposed 오류 없음)', () async { + final tmp = Directory.systemTemp.createTempSync('jj_disp_'); + addTearDown(() => tmp.deleteSync(recursive: true)); + final a = Directory(p.join(tmp.path, 'a'))..createSync(); + File(p.join(a.path, '1.txt')).writeAsStringSync('1'); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + final pair = LiveSyncPair(a.path, p.join(tmp.path, 't'), method: 'builtin'); + c.settings.liveSyncPairs = [pair]; + final live = LiveSync(c); + final running = live.syncNow(pair); + live.dispose(); + await running; // 예전에는 여기서 notifyListeners 가 FlutterError + expect(File(p.join(tmp.path, 't', '1.txt')).existsSync(), isTrue, reason: '맞추기 자체는 끝까지'); + }); }); testWidgets('모니터링 화면: 복사 목록 · 옵션 고치기 → 현재 유지 / 반영 후 실행, lsync 탭 · 일정', (tester) async {