カテゴリー「c#」

TAPベースに非同期処理に関連して、

ネットを調べると、「同期と非同期の混在によるデッドロック」みたいな記事が多い。

これ、よく調べてみると、.NET FRAMEWORKの話だったりして、

.NET(core以降)のASPでは、どうやら事情が違うようです。

(※GUIについては、ここでは触れません)

CUI(コンソール)アプリケーションについては、もともとSyncronizedContextがないから、

デッドロックしないよ、みたいな話はあるのですが、

ASP.NET Core以降は、SyncronizedContextがなくなったため、よくあるデッドロックというのは起きません。

(参考)

https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html

https://qiita.com/jun1s/items/c178d37a2cbcd8594461

というわけで、デッドロックしそうなコードをかき集めて検証してみました。

下記コードの検証環境は以下の通りです。

$ dotnet --version
8.0.204

$ cat /etc/os-release
NAME="Pop!_OS"
VERSION="22.04 LTS"

(パターン1)

app.MapGet("/test1", async () =>
{
    Console.WriteLine("test 1-------------------------");
    Console.WriteLine($"before - {Thread.CurrentThread.ManagedThreadId}");  // 1: スレッドIDは15
    var ret = MyClass.Get().Result;
    Console.WriteLine($"after - {Thread.CurrentThread.ManagedThreadId}");  // 4: スレッドIDは15
    return ret;
});

class MyClass
{
    public static async Task<string> Get()
    {
        Console.WriteLine($"before in - {Thread.CurrentThread.ManagedThreadId}"); // 2: スレッドIDは15
        var ret = await Task.Run(() => "ok");
        Console.WriteLine($"after in - {Thread.CurrentThread.ManagedThreadId}");  // 3: スレッドIDは14 ★ここでスレッドが変わっている
        return ret;
    }
}

(パターン2)

app.MapGet("/test4", async () =>
{
    Console.WriteLine("test 4-------------------------");
    Console.WriteLine($"before - {Thread.CurrentThread.ManagedThreadId}");  // 1: スレッドIDは9
    var result = MyClass.GetFromHttp().Result;
    Console.WriteLine($"after - {Thread.CurrentThread.ManagedThreadId}");   // 4: スレッドIDは9
    return result;
});

class MyClass
{
    public static async Task<string> GetFromHttp()
    {
        var client = new HttpClient();
        Console.WriteLine($"before in - {Thread.CurrentThread.ManagedThreadId}");  // 2: スレッドIDは9
        var response = await client.GetAsync("http://localhost:8080");
        Console.WriteLine($"after in - {Thread.CurrentThread.ManagedThreadId}");    // 3: スレッドIDは7 ★ここでスレッドが変わっている
        return response.Content.ReadAsStringAsync().Result;
    }
}

というわけで、色々勘違いしそうなんですが、

ASP.NET Core以降では、「同期と非同期の混在」によるデッドロックは起きないようです。

つまり、「ConfigureAwait(false)」がなくても良いのです。

※すいません、間違ってたら教えて下さい。

とはいえ、ライブラリアプリケーションの場合は、

どの実行基盤で使われるかわからないので、「ConfigureAwait(false)」はつけておいたほうが良いかと思います。

C#のasync/awaitは沼だなといつも思う。

わからないことだらけです。

ちょっとしか見てないんですが、Restsharp なんかでは内部でSyncronizedContextを持っているようで、

どうしてこうなっているのかは謎が深まるばかりです。

いつか、スッキリ整理してまとめたいと思うこの頃。

できれば、IO待ちに関しては、「AWAIT」ではなく「NOWAIT」にして欲しかったりする。

つまり、非同期をデフォルトってことですかね。(新しいMSの言語まで待ちかな?)

以上

投稿日時:2024年04月25日 00:43   カテゴリー:c#   [コメントがあればどうぞ]

掲題のものを実装してみました。

https://github.com/shigenobu/PurpleSofa

もともと、クライアントは1接続前提で作っていたのですが、

TCPのプロキシサーバーを作りたいなーと思い、

ベースライブラリとして、上記ライブラリに複数クライアントを束ねる機能を作ってみました。

PurpleSofaの特徴の1つとして、TCPセッションの最終受信時刻からアイドルタイムを経過すると、

TCPセションを強制的に切断するようにしています。

これはハーフCLOSE対策のためです。

サーバ側は、複数のTCPセッションをまとめて管理していたいのですが、

今回クライアント側も同じ様な対応を施しました。

で、そもそもとしてはTCPプロキシサーバーを作りたいというところなのですが、

長年お世話になっていたHAPROXYが、2系でコンテナで動かすと異常にCPUを消費するということがあり、

公式のISSUEでもあがっているのですが、なかなか完全対応というわけにはいかなそうです。

1系は問題ないので、仕事では1系を使ってますが、、もう古いので。。

意外なほどに、HAPROXYのようなヘルスチェックを兼ねているプロキシサーバーというものは少なく、

K8Sとかの大規模向けだと、どうも取り回しが悪いので、小さいものを作れないかなーと思い立ったわけです。

サンプルを作りつつ、ある程度目処はたったものの、

ヘルスチェックも様々あるので、ヘルスチェック部分をプラグイン化できないかと検討中です。

ちなみに、サンプルのコードは以下のとおりです。

https://github.com/shigenobu/PurpleSofa/blob/master/PurpleSofa.SampleProxy/Program.cs

ちょっとわかりにくいのですが、

上手く溜め込んで、バックエンド・フロントエンドとやり取りする

というのがポイントでしょうか。

要はプロキシはTCP端点をフロントとバックの2つで担当することになるので、

フロントとバックが繋がっていない状態を考慮して、メッセージを溜め込んでおく必要があるということです。

やっている人が少ないのですが、golangでは公式ライブラリとしてあるようなので、

そういった実装も参考にさせていただきました。

.NET8もリリースされたので、そろそろTCPプロキシサーバーを着手していければと考えています。

以上

投稿日時:2023年11月28日 14:03   カテゴリー:c#   [コメントがあればどうぞ]

ASP.NETで、JSONなどのように複雑な形のリクエストをバリデーションする場合、

一発で検証する方法がなく、苦労していた。

どうにか再帰的にできないかな〜と思っていたところ、

よいライブラリがあったので、紹介します。

https://github.com/tgharold/RecursiveDataAnnotationsValidation

こいつはとても素晴らしいです。(マイクロソフトがちゃんとやってほしい気もするが。。)

(入力定義)

public class Card
{
    [Required]
    public int? CardId { get; set; }
    
    [Required]
    public string CardName { get; set; }
}

public class Req
{
    [Required]
    [Range(0, 10)]
    public int? UserId { get; set; }
    
    [MyValidationCollectionLength(Min = 4, Max = 10)] // 独自のバリデーション
    public List<Card> CardList { get; set; }
}

(検証コード)

var req = new Req
{
    CardList = new List<Card>()
};
req.CardList.Add(new Card());
req.CardList.Add(new Card{CardId = 1, CardName = "hoge"});
req.CardList.Add(new Card{CardId = 2, CardName = ""});

var validator = new RecursiveDataAnnotationValidator();
var validationResults = new List<ValidationResult>();
var result = validator.TryValidateObjectRecursive(req, validationResults);

(検証結果をJSON化したもの)

[
  {
    "MemberNames": [
      "UserId"
    ],
    "ErrorMessage": "The UserId field is required."
  },
  {
    "MemberNames": [
      "CardList"
    ],
    "ErrorMessage": "The field CardList is invalid."
  },
  {
    "MemberNames": [
      "CardList[0].CardId"
    ],
    "ErrorMessage": "The CardId field is required."
  },
  {
    "MemberNames": [
      "CardList[0].CardName"
    ],
    "ErrorMessage": "The CardName field is required."
  },
  {
    "MemberNames": [
      "CardList[2].CardName"
    ],
    "ErrorMessage": "The CardName field is required."
  }
]

という具合に、ちゃんと再帰的に検証してくれます。

APIなんかで複雑なリクエストを検証するときは大いに機能するかと思います。

素晴らしいですね。

以上

投稿日時:2023年07月26日 17:02   カテゴリー:c#   [コメントがあればどうぞ]

最近知りました。

詳細については、以下を参考ください。

https://github.com/dotnet/runtime/issues/67214

本件ですが、

  • WINDOWSは問題なし
  • MACはダメ
  • LINUXはダメ

ということのようで、LINUXでやってみました。

環境は以下の通りです。

$ cat /etc/os-release
NAME="Pop!_OS"
VERSION="22.04 LTS"

$ dotnet --version
6.0.402

(1. 同期のダメな場合)

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            Console.WriteLine("main");
            throw new Exception();
        }
        catch (Exception e)
        {
            Console.WriteLine("error");
            throw; // ←こいつが原因
        }
        finally
        {
            Console.WriteLine("finally");   // ここを通過しない
        }
    }
}

(2. 同期でOKな場合)

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            try
            {
                Console.WriteLine("main");
                throw new Exception();
            }
            catch (Exception e)
            {
                Console.WriteLine("error");
                throw; // ←こいつがいてもfinallyに入る
            }
            finally
            {
                Console.WriteLine("finally");   // 上位のエラーハンドラーがいればOK
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("error - wrapper");
        }
    }
}

(3. 非同期の場合)

public class Program
{
    public static async Task Main(string[] args)
    {
        try
        {
            Console.WriteLine("main");
            throw new Exception();
        }
        catch (Exception e)
        {
            Console.WriteLine("error");
            throw; // ←こいつがいてもfinallyに入る
        }
        finally
        {
            Console.WriteLine("finally");   // async修飾子があればOK
        }
    }
}

(4. NET6からの簡易構文の場合)

try
{
    Console.WriteLine("main");
    throw new Exception();
}
catch (Exception e)
{
    Console.WriteLine("error");
    throw; // ←こいつがいるとダメ
}
finally
{
    Console.WriteLine("finally");   // ここを通過しない
}

以上のように、4つのパターンで試してみました。

asyncの場合は何も気にする必要はないですが、他パターンでは一考が必要かなと。

ちなみに以下のようなエラーになります。

Unhandled exception. System.Exception: Exception of type 'System.Exception' was thrown.

これって、コンテナでやるときにやばいかなと思う。。

気をつけてください。

また、シャットダウンハンドラー(以下のようなコード)を仕込んでみましたが、

動いたのは、「2. 同期でOKな場合」だけでした。

そもそも、asyncはシャットダウンハンドラーって使えない?(要調査)

AppDomain.CurrentDomain.ProcessExit += new EventHandler(ShutdownHander);
public static void ShutdownHander(object sender, EventArgs e)
{
    Console.WriteLine("shutdown");
}

(追記)

Unhandled exceptionは、ProcessExitには反応しないようです。

こちらを使う必要があります。

https://learn.microsoft.com/ja-jp/dotnet/api/system.appdomain.unhandledexception?view=net-8.0

以上

投稿日時:2023年05月18日 18:00   カテゴリー:c#   [コメントがあればどうぞ]

C#で、IPアドレスを取得する際、Dns.GetHostEntryを使うという記事がよく出てきます。

当然、DNSサーバを利用する前提なので、DNSサーバから正引きできないと、

結局は、ローカルループバックアドレスしか取れなかったりします。

ローカルネットワークであっても、

consulやdnsmasqなどをつかって、DNS設定くらいちゃんとしとけよというのはもちろんですが、

IPアドレスだけで済むなら、そうしたいのものかと思います。(WINSサーバについては不明です)

このように、正引きできないが、ローカルアドレスを取りたい場合、

NICを直接見に行くほうが楽です。

自分のTCPサーバ・クライアントライブラリに、将来的に使いそうな気がして、実装しておきました。

https://github.com/shigenobu/PurpleSofa/blob/master/PurpleSofa/PsNetwork.cs

v4のアドレス取得部分だけ抜粋します。

    private static readonly IEnumerable<NetworkInterface> NetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
        .Where(i =>
            i.OperationalStatus == OperationalStatus.Up &&
            i.NetworkInterfaceType == NetworkInterfaceType.Ethernet &&
            i.NetworkInterfaceType != NetworkInterfaceType.Loopback);

    public static IEnumerable<IPAddress> GetLocalIpv4Addresses()
    {
        List<IPAddress> addresses = new();
        foreach (var networkInterface in NetworkInterfaces)
        {
            var address = networkInterface.GetIPProperties().UnicastAddresses
                .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork)
                .Select(a => a.Address)
                .FirstOrDefault();
            if (address != null)
                addresses.Add(address);
        }

        return addresses;
    }

要は、起動中のイーサネットのNICだけを取得して、それをIEnumerableで返している感じです。

これでWIFIも取れるかと思います。(LINUXでは大丈夫だったが)

NICを複数持っているケース、

たとえばゲートウェイサーバのように、外向きと内向きのアドレスを持つ場合などについては、

https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.aspnetcore.httpoverrides.ipnetwork?view=aspnetcore-7.0

なんていうクラス(ASPですが)を使うことで、

取得したアドレスが、指定のアドレス範囲内かどうかを判断して、取捨選択も出来るかと思います。

以上

投稿日時:2023年01月12日 23:27   カテゴリー:c#   [コメントがあればどうぞ]

私が作成しているPurpleSofaの0.0.9で、IPv6対応をしました。

https://www.nuget.org/packages/PurpleSofa/

v6のサーバというのは、初めて作ったのですが、

フォーマットや省略ルールなど、以下のIBMのサイトがよくまとめられていたので、記載します。

https://www.ibm.com/docs/ja/i/7.1?topic=6-ipv6-concepts

C#の実装におけるポイントですが、

  • v4の「0.0.0.0」(anywhere)は、v6では「::」に相当
  • ソケットオプションで、v6でlistenする場合は、v6onlyをfalseにしておくとv4のソケットをv6として扱ってくれる

ぐらいでした。

(v6でlistenしても、v4を扱う設定)

_socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);

クラウドなんかでは、フロントとなるCDNとかロードバランサが、v6→v4変換とかもしれくれるので、

あんまりv6を直接扱う需要性は低いような気はしますが、

海外では、v6アドレスしか提供しないプロバイダなんかもあるようで、

ある程度v6の対応というか準備もしておいたほうがいいのかなと思うこの頃です。

以上

投稿日時:2023年01月12日 22:59   カテゴリー:c#   [コメントがあればどうぞ]

mysqlやmariadbからData Ware House(以下、DWH)へレプリケーションしたいな、って最近思います。

実際のところ、DWHがupdate/deleteといった行操作に弱いので、

なかなかDWHが使えない状況が続いていますが、

最近ではTiDBのTiFlushや、OCIのheatwaveといったもの(行操作も強そうなもの)が出てきているので、

OLTPのmysql/mariadbから、シームレスにレプリケーションいけそうじゃないか?

という感触を持っています。

とはいえ、update/delete問題が解決しても、実際はOLTPのデータベースと、

DWHをつなぐときは、マルチソースレプリケーションである必要があったり、

そもそも各種DDLを解決したりと、なかなか簡単には事が運ばない印象です。

そんなこんなで、Change Data Capture(以下、CDC)が使えるんじゃないかな?

とずっと思っていたのですが、C#にCDCのライブラリがあったので、紹介しておきます。

https://github.com/rusuly/MySqlCdc

まだ、動作検証はしてないですが、かなり色々できそうな気がしてます。

CDCのソフトウェアは有償・無償と色々ありますが、

実際にSQL(イベント)をアプリケーションで捕まえることができるようになるので、

ある程度mysqlのレプリケーションをしっているエンジニアであれば、

かなり面白いことができそうな気はしてます。

以上

投稿日時:2022年11月29日 17:22   カテゴリー:c#, mariadb, mysql   [コメントがあればどうぞ]

ArmadaSuitさんが、fluentd / fluent-bit にfowardするC#クライアントを作成してくれました。

(Pigeon)

https://www.nuget.org/packages/Pigeon/

シンプルなライブラリではありますが、fluentdの4modeをサポートしています。

そのため、受け手側に合わせて柔軟な変更が対応が可能というものです。

基本的には、MessageModeで十分ですが、FluentNettingのようなリモートのサーバと直通信する場合は、

CompressedPackedForward Modeを使うことで、サイズを圧縮できるなどのメリットがあるかと思います。

ぜひ、FluentNettingと合わせておつかいください。

(FluentNetting)

https://www.nuget.org/packages/FluentNetting/

以上

投稿日時:2022年01月27日 19:12   カテゴリー:c#, fluentd   [コメントがあればどうぞ]

表題のとおり、fluentd / fluent-bitのoutput forwardを受信するC#のサーバライブラリを、

nugetにリリースしました。

(FluentNetting)

https://www.nuget.org/packages/FluentNetting/

これは、javaで作られているinfluentというライブラリをC#に移植したような感じのものです。

(influent)

https://github.com/okumin/influent

※私が作ったのではありません。すばらしいライブラリです。

で、FluentNettingでは、fluentdのv1プロトコルがサポートする4つのmodeに対応しつつ、

hostname / shared_key による認証までを対応しました。

さらにいうと、fluent-bitもテスト済みでございます。

このFluentNettingを使うといいことは、「遅延処理が自由自在」ということです。

アプリケーション → fluend / fluent-bit → FluentNettingを使った受信サーバ

のようにして、fluentd / fluent-bit でバッファリングしておけば特に良いのかなと思います。

メッセージキューイング系を使うのと同じじゃね?と思うかもしれませんが、

fluend / fluent-bit はアプリケーションと同一ホスト(k8sならサイドカー)にする形が多いので、

アプリケーションからの書き込みタイムが少なくできる利点があります。

プラグイン使えば良くない?と思うかもしれませんが、

プラグインはメンテナンスされていないものもあり、

場合によってはアプリケーションエンジニアが柔軟に変更するのが難しいケースがあります。

という事情を踏まえると、

fluentd / fluent-bit は forwardさえやればよく(設定が簡単)、

プラグインに悩まされずにデータを処理できる、

という利点があるのかと感じています。


各言語の対応ライブラリですが、基本的には公式が出しているものを使えば利用できます。

(fluentdのライブラリ)

https://docs.fluentd.org/language-bindings

ただし、確認した限りでは、PHPのライブラリはパーサがデフォルトJSONになっているので、

msgpackに変更する必要があります。

C#については、

(FluentdClient.Sharp)

https://www.nuget.org/packages/FluentdClient.Sharp/

というものがあるのですが、EventTimeがdouble型になっているようで、

fluent-bitではエラーとなってしまうようです。(fluentdでは大丈夫のようです)

各言語とFluentNettingの対応状況は後々確認していこうとおもいます。


最後になりましたが、今回ArmadaSuitさんにパーサ部分に尽力いただきました。

彼のおかげで、今回リリースまでこぎつけられました。感謝です。

その彼が、C#のクライアントライブラリを作成してくれているので、

近々nugetにアップされるのではないかと期待しています。

以上

投稿日時:2022年01月19日 17:57   カテゴリー:c#, fluentd   [コメントがあればどうぞ]

TCPに引き続き、UDPを作ってみた。

https://github.com/shigenobu/OrangeCabinet

一応、nugetに公開済みです。

これも、JAVA版があるのですが。。一応紹介。

https://github.com/shigenobu/blueshelf

一応、mavenで公開済みです。

この、C#のUDPライブラリの特徴は、以下となります。

  • 例によって、ハーフクローズを検知用に、最終受信からN秒でタイムアウト(UDPはそんなもんないけど)を処理
  • クライアントでもbindする

一番目は、送信元が同じ限り、タイムアウトするまでは独自のオブジェクトで任意変数を格納できる。

(セッションみたいなイメージ)

二番目は、クライアントでも予めbindすることで、ポートを決め打ちできる。

connectだと、ポートが決め打ちできないので、あえてbindを使う形にしています。

なんでこんなもんをつくっているのかというと、

実は、TCP/UDPのサーバ機能を必要とするあるものを作りたいと考えているからです。

うまくできたら、nugetに公開しようかと考えています。

今回ちょっとハマったのは、BeginXXXとEndXXXは対になっているらしく、

BeginReceiveFrom

EndReceiveFrom

はセットで使う必要があるということでした。

ただ、BeginReceiveFromの部分でも、使わないremote endpointインスタンスを生成しないといけないのは、ちょっと微妙な気がした。

完了時に、remote endpointが取れればいいと思うのだが。。

また、システムコールとしては、epoll_createは使われていたが、UDPなんでepollである必要もないのかなとは思った。

JAVAだと、UDPに対するepollの実装はないので(正確にはJAVA7でお蔵入りしたようだが)。

概ね問題なさそうだが、一時オブジェクトの生成が少し多い気がしている。

IAsyncResultオブジェクトを毎回作っているような感じなので、そこは調整できればとは思う。

(ただ、毎回作らないと受信バッファの読み込みがうまくいかないので、簡単にはいかない気がしている。)

以上

投稿日時:2021年12月13日 22:24   カテゴリー:c#, java, network   [コメントがあればどうぞ]