SFTP CreateDirectoryAsync is not idempotent
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 38/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- csharp
- 領域
- networking
調査の方向性
SftpClient.CreateDirectoryAsync と既存の XML ドキュメントから開始し、ディレクトリがすでに存在する場合の動作を ExistsAsync および報告された SFTP 応答と比較します。要求された冪等な動作またはより具体的な例外のどちらがプロトコルに適合するかを判断します。完了とは、呼び出し元が既存のディレクトリを他の失敗と区別できることです。
索引モデルが issue の本文から書いたものです。
説明
My specifications:
OS: Ubuntu Desktop and Windows 11
Framework: .NET 9
Package version: 2025.1.0
This is my current code:
using SftpClient client = new SftpClient(host, port, username, password);
await client.ConnectAsync(cancellationToken);
try
{
await client.CreateDirectoryAsync(someDir, cancellationToken);
}
catch (SftpException ex)
{
// Only some logging code here.
}
When this directory already exists, all I get as an exception message is "failure".
Renci.SshNet.Common.SftpException: failure
at Renci.SshNet.SubsystemSession.<WaitOnHandleAsync> g__DoWaitAsync | 38_0[T](TaskCompletionSource`1 tcs, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at Renci.SshNet.SftpClient.CreateDirectoryAsync(String path, CancellationToken cancellationToken)
Problems:
- I can't know for sure if the exception is about the directory already existing, or whether something else is wrong. Type SftpException with message "failure" is just not descriptive enough.
- CreateDirectoryAsync is not idempotent. Throwing an exception seems very counter-intuitive for a method like this.
- The SftpException is not described in the method documentation:
/// <summary>
/// Asynchronously requests to create a remote directory specified by path.
/// </summary>
/// <param name="path">Directory path to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous create directory operation.</returns>
/// <exception cref="ArgumentException"><paramref name="path"/> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPermissionDeniedException">Permission to create the directory was denied by the remote host. <para>-or-</para> A SSH command was denied by the server.</exception>
/// <exception cref="SshException">A SSH error where <see cref="Exception.Message"/> is the message from the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
If I would want to do this properly, I need quite a lot of code. This is the extension method I wrote:
internal static async ValueTask EnsureDirectoryExists(this SftpClient client, string directory, CancellationToken cancellationToken)
{
bool directoryExists = await client.ExistsAsync(directory, cancellationToken);
if (directoryExists) return;
try
{
await client.CreateDirectoryAsync(directory, cancellationToken);
}
catch (SftpException ex)
{
try
{
bool directoryAlreadyExisted= await client.ExistsAsync(directory, cancellationToken);
if (directoryAlreadyExisted)
{
// The directory already existed, so we can ignore the exception.
return;
}
else
{
// The directory does not exist.
throw;
}
}
catch (SftpException)
{
// Failed checking if the directory exists.
// Throwing the original exception as that illustrates what really is the problem here.
throw ex;
}
}
}
There are 2 ways this could be fixed:
- Add an EnsureDirectoryExists method. I'm sure this can be done much closer to the actual SFTP protocol than the extension method I created.
- Add an SftpDirectoryAlreadyExistsException that inherits from SftpException.
I'm very curious, is there a reason why something like EnsureDirectoryExists was never added to SSH.NET?
- 主要言語
- C#
- スター
- 4.4k
- フォーク
- 994
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
sshnet/SSH.NET のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
-
難易度 5/5 1週間以上 初心者へのやさしさ 45/100
-
ShellStream.Expect over-discards on undecodable bytes, driving ArrayBuffer.ActiveLength negativeオープン
難易度 4/5 3〜5日 初心者へのやさしさ 52/100
-
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
似ている issue
-
area/navigationview 🧭 difficulty/starter 🚀 good first issue kind/bug platform/all project/navigation-lifecycle 🧬 triage/untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
unoplatform/uno#24925 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
DamianEdwards/ghcp-spend-tray#39 ·
メンテナーはふだん 1 日以内に返信
-
copilot documentation
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 2 日以内に返信
-
[Rust][Flaky Test] multiple_deadlines_fire_in_order asserts a wall-clock gap instead of firing orderオープンCI/CD ⚒️ Flaky-tests 🐦
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
valkey-io/valkey-glide#7255 ·
メンテナーはふだん 3 日以内に返信
-
bug good first issue
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
unoplatform/Uno.Core#99 ·