Azure Functionsに排他制御を入れる
FunctionsでFunction単位で排他制御したいことがあったので調べてみました
結論はBLOB作ってそれをロックでした(他にあるのかしらない)
BLOBのロックは
Microsoft Azure Storage でのコンカレンシー制御の管理 | Microsoft Docs
ここに書いてあります
ということで実験しましょう
HttpTriggerなFunctionを作る
こんな感じ
public static class Function1 { [FunctionName("Function1")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, [Table("Log")] CloudTable logTable, ILogger log) { string name = req.Query["name"]; for (var i = 0; i < 5; i++) { await logTable.ExecuteAsync(TableOperation.Insert(new LogEntity(name, i))); await Task.Delay(1000); } return new OkResult(); } public class LogEntity : TableEntity { public string Name { get; set; } public int Num { get; set; } public LogEntity() { PartitionKey = "Log"; RowKey = Guid.NewGuid().ToString(); } public LogEntity(string name, int num) : this() { Name = name; Num = num; } } }
テンプレートをいじってテーブルストレージ(Log)にデータを保存します
同時制御したいので書き込んで1秒待って、を5回繰り返します
あとはデバッグ実行して
http://localhost:7071/api/Function1?name=a http://localhost:7071/api/Function1?name=b
で呼び出し
書き込んだ値を時間でソートすると
二つのリクエストが同時に走っていることがわかると思います
排他制御を入れて順番に実行する
Run
関数にBLOBのロック処理を追加します
public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, [Table("Log")] CloudTable logTable, Binder binder, ILogger log) { string name = req.Query["name"]; // BLOBをロックすることで排他制御とする #region リース取得 var lockBlob = await binder.BindAsync<CloudBlockBlob>(new BlobAttribute("temp/lock")); if (!await lockBlob.ExistsAsync()) await lockBlob.UploadTextAsync("lock"); // 無ければ作っているけど事前に作っておいた方がいいと思う var lease = ""; for (var i = 0; i < 100; i++) // 20秒挑戦してダメなら終わろう { try { lease = await lockBlob.AcquireLeaseAsync(TimeSpan.FromSeconds(15), null); Console.WriteLine($"blob lease = {lease}"); break; } catch (StorageException storageException) { if (storageException.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Conflict) { Console.WriteLine("Wait for release lease..."); await Task.Delay(200); continue; } return new ObjectResult("Error") { StatusCode = (int)HttpStatusCode.Conflict }; } } if (string.IsNullOrEmpty(lease)) { // 取れなかったらエラーで終わる log.LogWarning("Can't get lease."); return new ObjectResult("Error") { StatusCode = (int)HttpStatusCode.Conflict }; } var accessCondition = AccessCondition.GenerateLeaseCondition(lease); #endregion for (var i = 0; i < 5; i++) { await logTable.ExecuteAsync(TableOperation.Insert(new LogEntity(name, i))); await Task.Delay(1000); } // リース解放 await lockBlob.ReleaseLeaseAsync(accessCondition); return new OkResult(); }
実行結果
順番に実行されるようになりました
注意点とか
コピペすると行数が膨らんで楽しい
BLOBのリース取得部分をクラスにしてもっと簡単に使えるようにしていた人がいました。探せば出てくると思うよ
リース期間に注意
AcquireLeaseAsync
に渡せるのは確か15~60秒だったはずなので注意
リース取得のリトライに注意
Functionsはデフォルトで5分長くても10分動き続けると強制的に落とされるので(Consumptionプラン時だけ?)何も考えずにロックしまくると大変だよ
結論
排他制御はやればできる
けどやらないに越したことはない
フォルダ名の最後がスペースのフォルダを作る (作れない)
作れません
知っている人は当たり前だと思うかもしれませんが、そんな名前のフォルダ作ろうなんて思ったこともなかったので知らなかったよ
試してみる
こうすると
こうなります。勝手に最後のスペースが消されます。警告とかそーいうの出ません
勝手に省略しないで
こっちの仲間に入れてあげればいいのに
C#でやってみる
// 最後がスペースのパス var path = @"g:\test "; try { Directory.CreateDirectory(path); // これは正常終了する } catch { Console.WriteLine("しっぱい"); // 例外とか吐かない } if (Directory.Exists(path)) // これもtrueが返ってくる { var file = Path.Combine(path, "a.txt"); // あるはずフォルダの下にテキストを書いてみる try { File.WriteAllText(file, "にゃーん"); } catch (Exception e) { Console.WriteLine(e.Message); // 「パス 'g:\test \a.txt' の一部が見つかりませんでした。」 } }
Oh...
せめてDirectory.Exists()
はfalse
を返してほしいんだけど・・なんか歴史的な経緯とかあるのかな~
GitHubから脆弱性あるよって報告が来た話
3か月ほど前に
ASP.NET CoreのSignalRでバイナリーデータをやり取りする - プログラムの事とか
ってやつを書いた時に作ったサンプル用のリポジトリにGitHubからAlertがやってきました。 こんなの初めてだったからドキドキしちゃった💛
GitHubのInsights->Alertsを見るとこんなものが
CVE-2018-8409はこちら
System.IO.Piplines
の4.5.0に脆弱性があったみたいですね
NuGetで最新にしてコミットして完了
通知見た時は誰か暇な人がIssueでも投げてくれたのかと思ったんだけど、GitHubが自動で解析みたいなことして警告出してくれているんですねー