その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

jitterをかけたtickerを提供するgoのライブラリjickerを書いた

github.com

定期的に実行したい何かがあって、そしてそのインターバルにjitterが入っていてほしいということがしばしばあり、必要な時に都度そういうコードを書いていたのですが、毎度書くのもしんどいな〜と思ったのでこの度ライブラリにしたという次第です。

READMEのUsageに書いてあるとおり、

package main

import (
	"context"
	"log"
	"time"

	"github.com/moznion/jicker"
)

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()

	c := jicker.NewJicker().Tick(ctx, 1*time.Second, 0.05)
	for t := range c {
		log.Printf("tick: %v", t)
	}
}

というふうに Tick() を使用すると、1秒に対して±5% jitterがかかったインターバル (すなわち0.95秒から1.05秒の間でランダムなインターバル) おきにtickしてくれるtickerを得ることができます。

package main

import (
	"context"
	"log"
	"time"

	"github.com/moznion/jicker"
)

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()

	c, err := jicker.NewJicker().TickBetween(ctx, 1*time.Second, 2*time.Second)
	if err != nil {
		log.Fatal(err)
	}
	for t := range c {
		log.Printf("tick: %v", t)
	}
}

一方別の関数 TickBetween() を使用すると、任意の区間でjitterがかかったインターバル (この場合1秒から2秒の間でランダムなインターバル) おきにtickしてくれるtickerがやってきます。


「実家」のような名前のライブラリですが今年のゴールデンウィークは実家に帰れません。世界が大変ですね。
小粒なライブラリですが、ぜひご利用くださいませ。

Goのhttptestパッケージを使ってUNIX domain socketを使ったHTTPサーバのテストをする

Goが提供するHTTPのテストのためのユーティリティであるところのhttptestを使って、UNIX domain socketを使用するHTTPサーバのテストをするという内容についてのメモです。

import (
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
)

func TestFoo(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		_, err := w.Write([]byte("hello"))
		if err != nil {
			log.Printf("error: %s", err)
			w.WriteHeader(500)
			return
		}
		w.WriteHeader(200)
	})

	tempSock, err := os.CreateTemp("", "sock")
	if err != nil {
		t.Fatal(err)
	}
	err = os.Remove(tempSock.Name())
	if err != nil {
		t.Fatal(err)
	}

	listener, err := net.Listen("unix", tempSock.Name())
	if err != nil {
		t.Fatal(err)
	}

	server := httptest.NewUnstartedServer(mux)
	server.Listener = listener
	server.Start()
	defer server.Close()

	// do something
}
  • os.CreateTemp()でtemporaryなファイルを作り、それを削除してファイル名だけを引っ張りつつ (削除せず実ファイルが残っているとbind(2)がEADDRINUSEを吐く)
  • それを net.Listen()に渡してlistenerを作り
  • httptest.NewUnstartedServer()でテスト用のサーバのインスタンスを作り
  • listenerをそのサーバのインスタンスに食わせ
  • サーバスタート
  • あとはUNIX domain socketを使ったクライアントを使ってテストを書く

という感じで使うことが可能です。簡単ですね。


もちろんtemp fileを使わずに任意のパスをソケットファイルとして与えても良い (server.Close()が実行されるとそのソケットファイルも削除されるので) のですが、うっかりserver.Close()が実行されないとゴミファイルが残ってしまったりしてダルいので、まあ保険のようなものです。

macOSでgit-send-emailがsmtpauthオプションを上手く扱えないという問題

brewでインストールしたgitのgit-send-mailsmtpauth を指定してもそれが上手く適用されず、問答無用で DIGEST-MD5 が利用されるという問題に当たったので、その解決方法をメモとして記します。
さくらのメールボックスMD5-DIGEST をサポートしておらず、PLAIN もしくは CRAM-MD5 を使う必要があるのでこの挙動は困るんですよね……

TL;DR

  • brewで入れたgitのgit-send-mailが依存しているCPANモジュールのバージョンが古い
  • 新しいバージョンのCPANモジュールがインストールされているPerlランタイムの PERL5LIBGITPERLLIB 環境変数に渡すと動く
    • 例:
$ GITPERLLIB="/path/to/.plenv/versions/5.32.1/lib/perl5/5.32.1/:/usr/local/Cellar/git/2.30.0/share/perl5" git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
  • もしくは依存している Net::SMTP のバージョンを上げる (例えば3.11) と動く

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H15
$ git version
git version 2.30.0
$ perl --version
This is perl 5, version 32, subversion 1 (v5.32.1) built for darwin-2level

なぜperlが必要かというとgit-send-mailはperlで実装されているためです。
なお、gitはbrewでインストールされており、おそらくこれも原因の一つです。

症状

$ git send-email 0001-something.patch
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

死んでまんな。そしてこのエラーメッセージは見覚えが……perlですね。

debug modeをenableにして再度見てみましょう。

$ git send-email --smtp-debug 1 0001-something.patch
...snip...
Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): y
Net::SMTP>>> Net::SMTP(2.31)
Net::SMTP>>>   Net::Cmd(2.29)
Net::SMTP>>>     Exporter(5.68)
Net::SMTP>>>   IO::Socket::INET(1.33)
Net::SMTP>>>     IO::Socket(1.36)
Net::SMTP>>>       IO::Handle(1.34)
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-DELIVERBY
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250 HELP
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>> AUTH DIGEST-MD5
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>>
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 235 2.0.0 OK Authenticated
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

依存しているCPANモジュールが軒並み古く、なおかつ >>> AUTH DIGEST-MD5 によって DIGEST-MD5 が指定されており、そして死んでいることがわかりますね (表面上は OK Authenticated が返ってきている一方、これは上手く動かない様子。参照: https://adiary.adiary.jp/0407)。


ウェブで検索をかけてみると、過去に同じような原因で詰まっており解決されている方が。

http://www.katsuster.net/index.php?arg_act=cmd_show_diary&arg_date=20180906&arg_range=1&arg_direction=0

なるほど〜、smtpauthPLAIN とかにしてみると良いんですね。現在はさくらのメールボックスCRAM-MD5 もサポートしているので、これを指定してみましょう。

$ git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-DELIVERBY
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250 HELP
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>> AUTH DIGEST-MD5
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>>
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 235 2.0.0 OK Authenticated
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

うーん、オプションで明示しても DIGEST-MD5 がまだ使われていますね。困った。

解決

とりあえずコード読んで、ちまちまデバッグしてみたところコード的には問題なさそう。つまり依存しているモジュールあるいはそのロードっぽいな〜、ということで git-send-email のコードを読んでみる。

#!/usr/bin/perl

use lib (split(/:/, $ENV{GITPERLLIB} || '/usr/local/Cellar/git/2.30.0/share/perl5:/Applications/Xcode.app/Contents/Developer/Library/Perl/5.18/darwin-thread-multi-2level:/Library/Developer/CommandLineTools/Library/Perl/5.18/darwin-thread-multi-2level'));
#
# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>

なるほど〜、 GITPERLLIB 環境変数が渡されていない時はなんか全体的に古いモジュールを見てそうですね。

というわけで GITPERLLIB に使っているperlPERL5LIB 相当を渡して実行してみましょう。

$ GITPERLLIB="/path/to/.plenv/versions/5.32.1/lib/perl5/5.32.1/:/usr/local/Cellar/git/2.30.0/share/perl5" git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
...snip...
Net::SMTP>>> Net::SMTP(3.11)
Net::SMTP>>>   Net::Cmd(3.11)
Net::SMTP>>>     Exporter(5.74)
Net::SMTP>>>   IO::Socket::IP(0.39)
Net::SMTP>>>     IO::Socket(1.36)
Net::SMTP>>>       IO::Handle(1.34)
...snip...
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-STARTTLS
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-DELIVERBY
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250 HELP
Net::SMTP=GLOB(0x7fb7d9825d38)>>> AUTH CRAM-MD5
...snip...
Result: 250
Net::SMTP=GLOB(0x7fb7d9825d38)>>> QUIT

モジュールのバージョンが新しめで、ちゃんと >>> AUTH CRAM-MD5 が指定され、そして成功していますね。やりました。
なお /usr/local/Cellar/git/2.30.0/share/perl5 も併せて指定している理由は、これが存在しないとgitが用意しているモジュールをロードできない為です。ここは適宜環境に合わせて変更する必要があるでしょう。

ちなみに、今回は新しめのバージョンのperlランタイムのライブラリパスを渡すことで解決しましたが、単純に Net::SMTP のバージョンを上げる (3.11とか)、でも解決できると思います。


というわけでようやく動きました……ところで git-send-email、実ははじめて使いました。以上です。

独自ドメインのメールアドレスを使うようにした

2021年の記事とは思えないタイトルですが、そのようにしたのです。

特定のメールサービスが提供するメールアドレスに依存していると、そのメールサービスからBANされた際に人権を維持できない可能性があります。というのも仮にメールアドレスが凍結すると、そのアドレスをアカウントのidentifierとして登録しているサービスを巻き込んでしまい大惨事が起きてしまいます。
プレッパーじみた危機意識ではありますが、そのような気持ちになったのでこのたび独自ドメインでメールアドレスを払い出し、それを使うようにしてみました。

しかし自前でpostfixを運用する……みたいなことは断固やりたくなかったので、今回はさくらのメールボックスを利用して、元々保有していたドメインサブドメインを使ったメールアドレスを払い出し、そこに送られてくる全てのメールをGmailへと転送するという構成を取りました。
メールボックスに保存せず、即座に転送するような設定とすることで、さくらのメールボックスの容量制限を考えずに済むので便利です。GmailのUI/Appをそのまま使えるというのもグッドポイント。これでGoogleから仮にBANされてもメール機能については「UIが使えなくなるだけ」というところで被害を食いとめることができますね。

自分のドメインにメール用のサブドメインを切り、そのサブドメインに対して以下のようにNSエントリを設定すると、ドメインの移管等をすることなくメール用にドメインが使えるのは簡単で良いですね。

subdomain    NS    ns1.dns.ne.jp.
subdomain    NS    ns2.dns.ne.jp.

なぜサブドメインをわざわざ使っているかというと、主ドメインを使おうとするとさくらが管理するネームサーバーを利用する必要があるのですが、しかし今使っているネームサーバーはCloudflareのものなので引越し等がだるい。しかし、サブドメインだとサブドメインだけNSレコードの設定 (僕の場合はCloudflareで設定する) によりネームサーバーを差し向けることができるので楽ちん、という感じの動機でした。


さくらのメールボックスの特筆事項としては以下のような感じ。

  • ユーザーを制限無く作れる

※1 同じメールアカウント名で異なるドメイン毎のメールアドレスの運用は利用できません。
(例:「info@◯◯◯.com」と「info@△△△.net」の場合、同じ「info」のメールボックスに配信されます)

さくらのレンタルサーバとメールボックスには同一のドメインを登録することはできません。
https://www.sakura.ne.jp/mail/

  • DKIMは使えない


というわけで最小労力で自ドメインのメールアドレスをゲットしましたから、今後はこれを使っていきたいと思います。
メールサービスの選定のようなものは以下にメモを残しておきました:
scrapbox.io

moznion@mail.moznion.net
メル友募集中です。ちなみに僕はメールを見るという習慣がありません。


[追記]

「メールをメールボックスに保存せずにGmailへ転送だけ行うと、GmailからBANされたときに送られてきたメールがロストするのでは?」という指摘があり、それはその通りだと思いますが、僕はまあそれで良いかなと思っています。GoogleからBANされたらメールだけでなくありとあらゆるものが大倒壊し、暮らしがめちゃくちゃなことになると思うので、メールのロスト程度はかすり傷でしょう……という心意気です。メール自体の機能よりも「安定したアカウントのIdentifier」として使いたいというところが大きいというのもありますね。
とはいえ心配であれば、メールボックスに保存した上で転送、という方法を採用するのも手だと思います。さくらのメールボックスは10GBもあるし、メール用途であれば実質無限と言っても差し支えなさそうですし。

eclipse/leshan で最近直した問題について

かなりニッチな話題ですが、OMA LwM2Mのサーバ・クライアントのJava実装であるEclipse Leshanにパッチを送って取りこまれたのでそのご報告です。

github.com

Leshanのクライアントライブラリには予期せぬ例外が発生した際にクライアントアプリケーションがスタックし、プロセスは生き続けるものの一切の仕事をしなくなるという問題がありました。

Better handle unexpected error in DefaultRegistrationEngine. · Issue #933 · eclipse/leshan · GitHub


例えば以下のようなコードの箇所でRuntimeExceptionが発生すると、エラーログが記録されるだけで他のエラーハンドリングが成されません。

https://github.com/eclipse/leshan/blob/leshan-1.2.0/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java#L557

この RegistrationTask は個別のスレッドで動くのですが、意図しない実行時例外 (例えばUDP通信のレイヤで失敗するなど) が発生した場合はこのスレッド (RegistrationTask) はそれ以降何もしない、かつ孤児のような状況になり、孤児スレッドは回収されずプロセスは生き続けることとなります。
LwM2M的に register が成功しない限りプロトコルとして意味のある通信はできませんから、つまりこの状態のクライアントアプリケーションは一生register処理を行うことのできない、無意味なプロセスと化します。


その他にも複数箇所同様の問題がある部分があったため、それらを修正したというのが以下になります。

これらの変更により、不慮の例外が当該箇所で発生した場合は自動的にリソースを開放する (つまりシャットダウンを正しく行なう) ようになるため、何もしないゾンビのようなプロセス (注: ゾンビプロセスではない) は基本的に発生しなくなります。


基本的、と記述した理由は利用者が独自に定義して利用している LwM2mInstanceEnabler 及び LwM2mObjectEnabler に対して適切に StartableStoppableおよびDestroyableというそれぞれのInterfaceを実装してあげる必要があるためです。
不慮の例外が発生した際にしっかりと終了させたい場合は Destroyable を実装する必要がありますし、ホットな状態での再起動処理を正しく行いたい場合は Startable および Stoppable を適切に満足させる必要があります。
この詳細についてはLwM2mInstanceEnabler及びLwM2mObjectEnablerjavadocをご参照ください。


これらの修正はバージョン1.3.0以降から利用可能となっています。

github.com

現在開発中の次世代バージョンである2.0.0については順次対応予定 (2.0.0-M1よりも新しいバージョンで) となっているようです。

以上です。ご活用ください。

awesome-perlのご紹介およびメンテナの大募集

こんにちは。id:moznionと申します。Hachioji.pmというIT技術コミュニティに所属しています。

本記事はPerl Advent Calendar 2020の記事として記述されています。前日の記事は@mihyaeru21さんのGitHub Actions で Perl を動かすときのテンプレートでした。


Hachioji.pmという名前からわかるように、ここは元来はPerlを書く人が多かったコミュニティなのですが、時代の推移によりPerlを書く人は徐々に少なくなりつつあります。かく言う私自身も、かつてはPerlでそこそこ大規模なWebアプリケーションを書いて糊口を凌いでいましたが、ここ最近は仕事で (というかそこそこ規模の大きなコードを) Perlを書いたことは久しくありません。
Perl Advent Calendarなのになにを突然不敬なことを言い出すのかという感じですが、これは本筋に深く関係のある話のため記しました。


さて、Hachioji.pmではawesome-perlと呼ばれる、いわば「素晴しいCPANモジュール」のキュレーションプロジェクトのようなものを運用しています。元々は個人で運用されていたプロジェクトだったのですが、様々な事情があってHachioji.pmに運用が移管されたという経緯であったと認識しています。

github.com

awesome-goawesome-dockerなどといった、いわゆるawesome-*perl版だとお考えください。

awesome-perlはおかげさまで、多くの人に参照され活用されているようであります。モジュール追加等のpull-requestも定期的に送られてきておりありがたい限りです。


しかし、先に書いたように、Hachioji.pmではPerlを主戦場としているメンバーが少なくなってきており、awesome-perlのメンテナンスに一抹の不安を覚えるようになりました。
現状、メインでawesome-perlのメンテナンスを行っているのは私とid:uzullaさんの2名なのですが、私は先に述べたようにPerlからは一線を退いたに近い状況ですし、uzullaさんはPHPを主として活動されている方 (もちろんPerlは深く理解されています) となるため、両名とも現代のPerlのエキスパートとは言いがたい状況になっています。

その結果として、awesome-perlに送られてきたpull-requestについてはフォーマット等の議論・指摘はするものの、その内容についてはほぼ議論がバイパスされ、アッサリとマージされるようになってきているというのが近況となっています。廃墟にはしたくないのでpull-request自体のハンドルはされている、というような状況だとお考えください。
これについては質の観点での懸念があります。本来であれば提案されたモジュールの内容について議論をして精査し、然るのちにマージされるべきです。なんでもかんでも受け入れていては掃き溜めになることは避けられず、キュレーションとしては価値が失なわれてしまうためです。
しかし、何度か触れたようにPerlを現場で利用している人が少なくなってきているため、この健康的な手順を踏むことが徐々に難しくなってきています。


というわけで単刀直入に言いますと、awesome-perlを一緒にメンテナンスしてくれるメンバーを募集しています。現在も現役でPerlのプロジェクトを行われている方であれば非常にありがたいですが、そうでなくともメンテナンスを手伝ってくれる意思のある方であれば大歓迎です。

もし興味がある方がいましたら、私に直接連絡していただいても結構ですし、Hachioji.pmのSlackに来ていただいても歓迎です (普段は雑談をしているSlack Teamです)。


以上です。よろしくお願いします。awesome-perlでサクセスしませんか。

RADIUSのサーバー・クライアント実装をRustで書いた

An English article is here: https://dev.to/moznion/released-radius-rs-2e1o

タイトルの通り、AAA (Authentication, Authorization and Accounting) のためのデファクト・スタンダードプロトコルであるところのRADIUSのサーバー・クライアント実装をRustで書きました。

github.com

また、crates.io にもpublishしてあります: https://crates.io/crates/radius

このRADIUS実装の特徴としてはtokioを使用することで非同期 (async/await) ネイティヴな実装になっていることが挙げられます。
また、RADIUSgolang実装であるところのlayeh/radiusを参考にして、FreeRADIUSのdictionaryをコード生成器に食わせることによって、RADIUSアプリケーションの構築で必要となるRustの定義ファイルがRFCに基づいて自動生成されるようになっています。

簡単なサーバーとクライアントアプリケーションのサンプルは以下のような感じです。

割と簡単に書けて良いですね。


なんやかや、今でもRADIUS使うことってあるじゃないですか? もしも突然、RustでRADIUSアプリケーションを書く必要が出てきた際にはぜひご利用ください。

ところで、Rustでそこそこの規模感のあるコードを書いたのは実際初めてみたいなものなので実装上の問題などあるかもしれませんが、その場合はissue等でご指摘いただけると助かります。