Skip to content

Commit 0be0ce7

Browse files
authored
fix(sftp): close persistent SFTP channel on page dispose and guard async race (#1173)
* fix(sftp): close persistent SFTP channel on page dispose and guard async race - Close _status.client in dispose() to prevent channel leak when navigating away from the SFTP browser page. - Replace ??= with explicit null check to avoid concurrent double-open race when multiple _listDir calls overlap. - Add catch block to close and null-out stale client on non-SftpStatusError, allowing recovery on next call. * fix(sftp): add single-flight future guard for SFTP session init - Introduce _openingClientFuture to prevent multiple concurrent _client.sftp() calls when rapid navigation races the lazy _status.client initialisation. - Reset _openingClientFuture on dispose and after errors. - Add mounted check after awaiting the future so unmounted pages do not attempt further UI operations.
1 parent 2df593c commit 0be0ce7

1 file changed

Lines changed: 21 additions & 5 deletions

File tree

lib/view/page/storage/sftp.dart

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class _SftpPageState extends ConsumerState<SftpPage> with AfterLayoutMixin {
6666
_SortOption? _sortedFilesOption;
6767
bool? _sortedFilesShowFoldersFirst;
6868
List<SftpName>? _sortedFilesCache;
69+
Future<SftpClient>? _openingClientFuture;
6970

7071
bool get _useSudo => _sudoHelper.enabled && _sudoMode.value;
7172

@@ -84,9 +85,11 @@ class _SftpPageState extends ConsumerState<SftpPage> with AfterLayoutMixin {
8485

8586
@override
8687
void dispose() {
87-
super.dispose();
88+
_status.client?.close();
89+
_openingClientFuture = null;
8890
_sortOption.dispose();
8991
_sudoMode.dispose();
92+
super.dispose();
9093
}
9194

9295
@override
@@ -1033,17 +1036,23 @@ extension _Actions on _SftpPageState {
10331036
}
10341037

10351038
try {
1036-
_status.client ??= await _withSftpOpTimeout(
1037-
'open browser session',
1038-
_client.sftp(),
1039-
);
1039+
if (_status.client == null && _openingClientFuture == null) {
1040+
_openingClientFuture = _withSftpOpTimeout(
1041+
'open browser session',
1042+
_client.sftp(),
1043+
);
1044+
}
1045+
_status.client ??= await _openingClientFuture;
1046+
_openingClientFuture = null;
1047+
if (!mounted) return null;
10401048
final client = _status.client;
10411049
if (client == null) return null;
10421050
return await _withSftpOpTimeout(
10431051
'list directory',
10441052
client.listdir(listPath),
10451053
);
10461054
} on SftpStatusError catch (e) {
1055+
_openingClientFuture = null;
10471056
final canFallback =
10481057
_sudoHelper.enabled &&
10491058
(e.code == 3 || _sftpPermissionDeniedReg.hasMatch(e.message));
@@ -1054,6 +1063,13 @@ extension _Actions on _SftpPageState {
10541063
final items = await _sudoHelper.listDir(listPath, password: pwd);
10551064
_sudoMode.value = true;
10561065
return items;
1066+
} catch (e) {
1067+
if (e is! SftpStatusError) {
1068+
_status.client?.close();
1069+
_status.client = null;
1070+
}
1071+
_openingClientFuture = null;
1072+
rethrow;
10571073
}
10581074
}
10591075

0 commit comments

Comments
 (0)