ごく普通のBlobトリガーを作成します。
public static class BlobTriggerHoge {
private static ILogger logger;
private static Foo foo;
[FunctionName("BlobTriggerHoge")]
public static void Run(
[BlobTrigger("container/{fileName}")] BlockBlobClient myBlobClient,
string fileName,
ILogger log,
) {
logger = log;
foo = new Foo(fileName);
a();
a();
a();
}
public static void a() {
logger.LogInformation($"fileName:{foo.fileName} hashCode:{foo.GetHashCode()}");
}
}
class Foo{
public string fileName;
public Foo(string fileName) {
this.fileName = fileName;
}
}
同じログを3回出しているだけに見えますね。
ところがこのFoo、たまに途中から別のインスタンスになることがあります。
[2024-01-01T00:00:00.001Z] fileName:hoge.png hashCode:123456789
[2024-01-01T00:00:00.002Z] fileName:hoge.png hashCode:123456789
[2024-01-01T00:00:00.003Z] fileName:fuga.png hashCode:987654321
なんで????????????
大量のファイルを同時にアップロードして、BLOBトリガーが同時に複数起動したときに起きたり起きなかったりします。
対策
Fooクラスに状態を持たせない。
よし、staticおじさんになろう。
原因
いやまあよくあるstaticの罠なのですが、これAzure Fucntionsのドキュメントに従って作ってたら絶対引っかかるだろ。
なにせありとあらゆるドキュメントでstatic Run()
とか書いてあるので、トリガーは静的でなければならないと思い込まされてしまうわけですよ。
実際はトリガーは静的でなくても動きます。
ドキュメントではたいへんひっそりと触れられていました。
https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library?tabs=v4%2Ccmd
https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-dotnet-class-library?tabs=v4%2Ccmd
The above example shows a static method being used, but functions aren't required to be static.
上記の例では静的メソッドが使用されていますが、関数を静的にする必要はありません。
先に言えや。
ということでBlobTriggerHogeクラスからstaticを全部消して普通のクラス・インスタンスにしたところ、インスタンスが入れ替わることがなくなりました。
実のところはインスタンスが入れ替わっているのではなく書き替えられています。
private static Foo foo
のfoo
は一見privateであり他所からは全く見えないようになっていると思えますが、実際は中身が複数のリクエストで共有されているみたいです。
そのせいで、トリガー1がhoge.png
を書きこんだ後でトリガー2がfuga.png
を書きこむと、その後はトリガー1からもfuga.png
になってしまうわけです。
え、そんなの常識だって?
そうですね。
感想
私はC#をAzure Functions上でしか使ったことがありません。
そもそもC#は必要にかられて仕方なくやっているだけなので、とりあえず動かすのに必要なところをつまみ食いしているせいで、体系的な学習を一切していません。
なので言語の基礎や定石や風習といったところが全然わかっていないんですよね。
そんなわけで見事にstaticの罠に引っかかってしまったという話でした。
PHPerはマルチスレッドに弱いのだ。
ところでこれ、static Run()
としつつ正しい処理にするにはどうすればいいんですかね?