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

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

令和最新! Linux Desktop環境上の tmux (3.3a) でOSのクリップボードとtmuxのコピペバッファを直結する

Linux Desktop上でterminalを使っている時にOSのクリップボードとtmuxのコピペバッファを直結させたいという話です。
つまり Prefix + [ 等でtmux上で範囲選択してEnterを叩いた時にその内容がOSのクリップボードに格納され、Prefix + ]でペーストする時はOSのクリップボードから引っぱってきて貼り付ける、という挙動にしたいのです。なんでそういう挙動にしたいかというと、そういう設定で長らくmacOS上で生活してきたので……

TL;DR

Wayland環境の場合、xselxclipを使った方法は上手く動きません。wl-clipboardで提供されるwl-copywl-pasteを使わないと駄目でした。

example:

set -s copy-command 'wl-copy'
bind ] run "tmux set-buffer \"$(wl-paste)\"; tmux paste-buffer"

とりあえずこれで動いているという状況。

以下メモ。

tmux 3.2以降では copy-command が使える

tmuxのGitHub RepositoryのWikiにはClipboardという名前のそのものズバリな聖典があり、基本的にはこれを読むとほとんど解決します。なのでこれを読みましょうという感じなのですが、

tmux 3.2 introduced an option called copy-command to set a command to pipe to for all key bindings. This is used when copy-pipe is called with no arguments which is now the default. If the option is empty, the copied text is not piped.

To pipe to xsel(1):

set -s copy-command 'xsel -i'

The more complex configuration in the next section also works with tmux 3.2 and later versions.

https://github.com/tmux/tmux/wiki/Clipboard#how-to-configure---tmux-32-and-later

とあるように、tmux 3.2以降ではcopy-commandという設定項目が導入されているのでこれを使うと手っとり早いです。あらゆるモードでのtmux上でのコピー時に、これで指定したコマンドがフックされて実行されます。つまり、OSのクリップボードにコピーした内容を叩き込むコマンドをここに指定すると良いということです。上記の例では xsel が使われていますが、Wayland環境だと上手く動かなかったのでwl-copyに変えてあげる必要があるでしょう。
古いバージョンのtmuxの場合はドキュメントに書かれているようにもうちょい複雑な設定を細々書かなきゃ駄目で面倒なので、copy-commandがあって良かったですね。

ただこれはあくまでコピーの設定なのでペーストは別途設定する必要があり、ペーストについては聖典にもあんま有効な情報は書かれていません。
なので素朴に

bind ] run "tmux set-buffer \"$(wl-paste)\"; tmux paste-buffer"

とすることで Prefix + ] 時に wl-paste を実行してOS側のクリップボードから内容を引っぱってきて、それをtmuxのpaste bufferにつっこむことで実現しているという感じです。X11環境であればxsel/xclipも使えるのではないでしょうか。

set-clipboard を使えば良いのでは?

set -g set-clipboard on のようにしてset-clipboardを有効にしてやると、

that on both makes tmux set the clipboard for the outside terminal, and allows applications inside tmux to set tmux's clipboard (adding a paste buffer).
https://github.com/tmux/tmux/wiki/Clipboard#changing-set-clipboard

とドキュメントにも書いてあるようにtmux外のクリップボードと連携し、paste bufferについても連携するようであります。
一見するとこれで良いじゃん! となるのですが、set-clipboardのHow it worksのセクションに書かれているようにOSC 52に対応しているターミナルエミュレータを使う必要があります。まあ使えば良いんですが、使っているターミナルがOSC 52に対応していなかったので今回は諦めることに。
というか、試しにOSC 52対応のKittyをインストールして set-clipboard をonにして使ってみたのですが、tmux上でのコピーはOSのクリップボードへ行く一方で、OSのクリップボードからtmuxのpaste bufferに入れられないという挙動になってしまっており、まあなんか厳しいなと思って撤退しました。このへんそこまで深く検証してないです。

まとめ
  • 結局、tmux上のコピーイベントとペーストイベントにフックして、OSのクリップボードと適切にやりとりするソフトウェアを呼び出してインターフェイスする方法が安定する
    • Waylandだとwl-clipboardが提供するツールを使う必要がある。X11だったらxselとかxclipとかは依然使えそう。
  • OSC 52対応端末だと set-clipboardをonにするともしかしたら一発で動くかもしれない (軽く試した感じ駄目だった)。

S3にホストされているMaven RepositoryをMaven (pom.xml) から参照する

S3にホストされているprivateなMaven Repositoryというものがあり、それをMaven (pom.xml) から参照する必要があったのでの方法についてのメモ。Gradleとかsbtとかからは割と使ったことはあったのだけれど、Mavenからは無かったので……

<project>
    ...

     <repositories>
        <repository>
            <id>your-repository-name</id>
            <url>s3://path/to/your/repository</url>
        </repository>
    </repositories>

    ...

    <build>

        ...

        <extensions>
            <extension>
                <groupId>com.github.seahen</groupId>
                <artifactId>maven-s3-wagon</artifactId>
                <version>1.3.3</version>
            </extension>
        </extensions>

        ...

    </build>
</project>

というふうに pom.xml 書いてあげると s3://path/to/your/repository でホストされているpackagesを dependencies から参照できるようになります。 maven-s3-wagon のバージョンについては適宜ドキュメントやリリースノートなどを見て調整しましょう。

AWSのアクセストークンについては READMEのAuthenticationのセクション の通りに解決されるので、そこを読むと良いでしょう。

簡単でしたね。良かった良かった。

Zigのビットシフト演算がちょっと面白い

なんとなくZigを触っているのですが、ビットシフト演算が独特の挙動で面白かったです。

const n: u8 = 0b00000001;
const shifted: u8 = n << 1;
std.debug.print("{b}\n", .{shifted}); // => 0b00000010

これは直感的なコードでしょう。n が1バイト左にシフトしています。

さて以下のコードはどうでしょうか。

const n: u8 = 0b00000001;
const shifted: u8 = n << 8;
std.debug.print("{b}\n", .{shifted}); // expects 0, but...

このコードは

./main.zig:5:30: error: integer value 8 cannot be coerced to type 'u3'
    const shifted: u8 = n << 8;

というコンパイルエラーが返却されます。u8型の値に対しては u3 型すなわち高々 7 までの整数値でのみシフトできるということです。たしかに8 bitの値なんだから最大7 bitシフトできれば良いでしょう、ということのようです。なるほど。
これはシフト対象の型 T に対し、シフトbit数の型が Log2T と定義されているからです。Log2T 型って面白いですね。u3 みたいな型を今まであまり見かけたことが無かったので斬新でした。まあこれが便利かどうかでいうと賛否ありそうな感じはしますね。

// generates the u3 random number
var rnd = std.rand.DefaultPrng.init(@intCast(u64, std.time.milliTimestamp()));
const random_num: u3 = rnd.random().int(u3);
std.debug.print("[debug] rand: {}\n", .{random_num});

const n: u8 = 0b00000001;
const shift_amout: u3 = 7;
const shifted: u8 = n << (shift_amout + random_num);
std.debug.print("{b}\n", .{shifted});

ではこのような、実行するまでシフトするbit数が不定の場合はどうなるでしょうか。
生成された乱数が 0 だった場合は単に7 bitシフトして 0b10000000 が無事得られますが、1以上だった場合は……

[debug] rand: 2
thread 431510 panic: integer overflow
/private/tmp/main.zig:10:39: 0x1005e6727 in main (main)
const shifted: u8 = n << (shift_amout + random_num);
                                      ^
/usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:551:22: 0x1005e9b7c in std.start.callMain (main)
            root.main();
                     ^
/usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:495:12: 0x1005e6de7 in std.start.callMainWithArgs (main)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/usr/local/Cellar/zig/0.9.1_1/lib/zig/std/start.zig:460:12: 0x1005e6d25 in std.start.main (main)
    return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
           ^
???:?:?: 0x10302351d in ??? (???)
???:?:?: 0x0 in ??? (???)
Abort trap: 6

このようなruntime panicが発生します。キャー

とはいえ、そうは言っても様々な理由で Log2T 型 以上のbit数でshiftしたい時もあるじゃないですか、どうすれば……

const n: u8 = 0b00000001;
const shift_amout: u3 = 7;
var shifted: u8 = n << (shift_amout);

var additional_shift_amount: u3 = 1;

var i: u3 = 0;
while (i < additional_shift_amount) {
    shifted = shifted << 1;
    i += 1;
}

std.debug.print("{b}\n", .{shifted}); // => 0

というわけでこうです。追加でシフトしたいbit数 (上記の場合 additional_shift_amount) ぶんだけループで1 bitずつシフトしてあげることでコンパイルエラーとruntime panicを回避することができます。良かった良かった。

ちなみに左シフトをしつつその操作によるオーバーフロー・アンダーフローの発生場合を検出したい時には @shlWithOverflow() という組み込み関数が利用できます。

[追記]

一旦整数のbit数を拡張して、十分な空間でシフトしてから元の型にtruncateするという方法。これも (むしろこれで) 良さそうですね。ありがとうございます。

[追記ここまで]




なお、なんとなくZigを触った結果としてはこのようなライブラリができつつあります。

書評: ISUCONを「ゴール」で終わらせない。『達人が教えるWebパフォーマンスチューニング ~ISUCONから学ぶ高速化の実践』

著者のid:catatsuyさんよりご恵投いただきました。ありがとうございます。実は著者の方から本を頂戴するのってはじめてです。

さて、この書籍のタイトルをはじめて見たときは「オッ、ついにISUCONの攻略本が来ましたね、これでワシも優勝間違いなしや!!」と思ったものですが実際に手に取ってみると必ずしもそうではないことに気付きました。むしろ「ISUCONで勝つための小手先のテクニック」のような話題は極力排除されており、高速かつ高可用なWebアプリケーションをどのように構築・運用していくか、というような実戦的な内容がその多くを占めています。

まず書籍の冒頭では「『Webアプリケーションのパフォーマンス』の定義」から始まり、「なぜWebアプリケーションが高速かつ安定して動作する必要があるのか」という目指すべき方向に対する動機付けが成されています。そういったハイパフォーマンスWebアプリケーションの構築・運用を模したコンテストがISUCONであり、「そのISUCONにどのようなアプローチを以って攻略していくか」というところから逆説的に「ハイパフォーマンスWebアプリケーションの構築・運用」を学ぶという筋書になっています。


「パフォーマンスに問題がある際は必ず計測をし、正しくデータを比較し、そこで明かになったボトルネックにのみ対応していく。勘や憶測で直す部分を決めるのは無駄なのでやらない」といったパフォーマンスチューニングの王道を下敷きとし、パフォーマンスを計測するための負荷試験のプランニングと実施方法、負荷に対するモニタリングの手法、その計測したデータをファクトとしたボトルネックの特定、そしてそのボトルネックに対する具体的な対応、といった内容が体系的に書かれています。

この中でもモニタリングの項が象徴的で、「ログのモニタリング」や「エージェントを使ったモニタリング」などといった継続して長期的にモニタリングを行なうための実践的な内容がおさえられており、ぶっちゃけこのへんってISUCONではやらないというか個人的にはISUCON競技中は目視でdstatの内容とかhtopの内容とか見て済ませがちなんですけど、こういった「実務で使える『ちゃんとした』モニタリング」に多くの紙面が割かれているのはISUCONに使えるテクニックの紹介だけを目的としているのではなく、その先にある「実際のWebアプリケーションを良くしていく」という部分をしかりと見据えているように感じました。


また、負荷試験のプランニングや実施については案外体系的・網羅的な情報が巷に無いように感じていたところ、本書籍ではそのへんが上手く言語化されていて非常に参考になりました。一方で、この書籍でもそうだったんですが負荷試験の項目ってアプリケーションの内容とアクセス特性を知った上でホワイトボックス的に作られがちですよね。ただ実際にアプリケーションを運用していると予期しないアクセスパターンがあったり意図せぬパフォーマンスの秘孔みたいなものがあったりするというのがままあると思っていて、そういうのをこう、探索的に負荷試験のプランニングをしていく方法みたいなのってあるんでしょうか? 毎度頭を悩ませております……


あと特筆すべきはデータベースの章で、著者のid:kazeburoさんのDBのパフォーマンス問題に対する試行・思考が文章としてトレースされている大変貴重な内容だと思います。常日頃から my.conf kazeburo 最強 などとGoogleで検索してはkazeburoさんの秘伝のタレをパクってきてカスタムして使っている身としては非常にありがたいものです、いつもお世話になっております。この章は声に出して読みましょう。
それはそうとしてこの章では言及されていあんかったのですが、ISUCONでよくボトルネック爆弾として設置されているさまじい多重JOINだとか、恐るべき量のサブクエリとかってどうやってほどいて (解決して) いますか? ああいうのを見ると本能的に脳が理解を拒んでしまうのですが、やはり気合と根性でやるしかないのでしょうか……あとこの手のやつってDBレベルでなんとか解決するべきなのか、あるいは別の方法 (例えばRedisとか) でやるべきなのか、といった見切りを付けるのもなかなか難しいところですよね。

キャッシュの章については拙スライドを多く引用・参照していただいてありがとうございました。こういった書籍に自分が書いた内容が載っているというのはありがたいというか、面映ゆいというか、なんだか不思議な感情になりますね。スライドはコレです、もう5年前になるのかこれ……:

また、CDNを活用するパターンの話があったり (これもISUCONというよりは実戦寄りの内容という印象)、Linux Kernel・Kernel Parameterの基礎的な内容があったりと内容が多岐に渡っており面白かったです。ISUCONのベンチマークの作り方については惜しげもなく大公開されていたため技術的にベンチマーカーは作れそうな一方で、実際の問題を作るにあたってはどうすれば? (パフォーマンスが終わっている、しかし筋道立てた解決方法があるアプリケーションを故意に作っるには?) という部分には触れられていなかったので、これは続編の書籍に期待したいと思います!




というわけで、ISUCONはゴールではなく、ISUCONで得た知識・経験を実際のアプリケーションに還元し、ワンランク上を行くことこそが真のGOAL……
『達人が教えるWebパフォーマンスチューニング ~ISUCONから学ぶ高速化の実践』はISUCONを題材としながら、実際のWebアプリケーションをどのように高可用なハイパフォーマンスWebアプリケーションとして構築・運用していくかを体系的に解説している大変良い書籍でした。すなわちこれはISUCONで勝つための単なるTips集ではなく、本チャンのWebアプリケーションを地道に良くしていくための知識の集合であり、つまり本チャンのWebアプリケーションを常日頃からハイパフォーマンスにしていればISUCONにも勝てるはず……この本を買って、毎日実践して、ISUCON勝ちましょう!!!

しかしこんな体系的かつ網羅的なISUCONの本があるなんて本当に良い時代ですね、僕もISUCON 3とかの時代にこれを読んでサクセスしたかった……これからはパフォーマンスチューニングに悩んでいる人がいたら恵比須顔でこの本をおすすめしたいと思います。あとぜんぜん知らなかった内容が普通にポロポロあったんで勉強になりました、それがどういう内容か、ここから先は君の目で確かめてくれ!

protoc-gen-java-dynamodb書いた / protoc pluginはじめて書いた

protobufでスキーマを書いて protoc-gen-java-dynamodb に食わせると、そのスキーマJavaの生成コードに「生成されたDynamoDBのエンティティクラスのコード」を良い感じにねじ込んでくれるというprotocプラグインを書きました。

syntax = "proto3";

package com.example.dynamodb;

import "path/to/protoc-gen-java-dynamodb/protos/options.proto";

option java_package = "com.example.dynamodb";
option java_outer_classname = "ExampleEntityProto";

option (net.moznion.protoc.plugin.dynamodb.fileopt).java_dynamodb_table_name = "example-entity-table";

message ExampleEntity {
  string hash_key = 1 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_hash_key = true];
  int64 range_key = 2 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_range_key = true];
  bool bool_var = 3;
}

例えばこのようなproto3を書いてプラグインに食わせると、 ExampleEntityProto.ExampleEntity.DynamoDBEntity というinner classとして以下のようなコードが生成されます。

    // protoc-gen-java-dynamodb plugin generated (((
    @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable(tableName = "example-entity-table")
    public static class DynamoDBEntity {
      private String hashKey;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey()
      public String getHashKey() {
        return this.hashKey;
      }

      public void setHashKey(final String v) {
        this.hashKey = v;
      }

      private Long rangeKey;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey()
      public Long getRangeKey() {
        return this.rangeKey;
      }

      public void setRangeKey(final Long v) {
        this.rangeKey = v;
      }

      private Boolean boolVar;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      public Boolean getBoolVar() {
        return this.boolVar;
      }

      public void setBoolVar(final Boolean v) {
        this.boolVar = v;
      }

      public DynamoDBEntity() {}

      public DynamoDBEntity(final String hashKey, final Long rangeKey, final Boolean boolVar) {
        this.hashKey = hashKey;
        this.rangeKey = rangeKey;
        this.boolVar = boolVar;
      }

      public ExampleEntity toExampleEntity() {
        return new ExampleEntity.Builder()
          .setHashKey(this.hashKey)
          .setRangeKey(this.rangeKey)
          .setBoolVar(this.boolVar)
          .build();
      }

    }

    public DynamoDBEntity toDynamoDBEntity() {
      return new DynamoDBEntity(this.getHashKey(), this.getRangeKey(), this.getBoolVar());
    }

    // ))) protoc-gen-java-dynamodb plugin generated

なのでprotobufでdeserializeしたオブジェクトに対して例えば obj.toDynamoDBEntity() としてやると生成された DynamoDBEntityインスタンスに変換ができ、それをDynamoDBMapper等に食わせることが可能です。逆に DynamoDBEntityインスタンスにも toSomethingActualClass() (SomethingActualClass の部分は実際のクラスによって変わります) という変換コードが生えているので、実際のクラスのオブジェクトに変換することも容易です。

くわしい使い方についてはREADMEをご参照ください。


モチベーションとしては、例えばDynamoDB Streamsを使っている時なんかに良くありがちですが「データをインサートするコンポーネント」と「ストリームに乗ってきたデータを処理するコンポーネント」とが別々の言語で記述されていると、それぞれのコンポーネントでテーブル定義を二重に定義しなければならない、みたいな状況になることがあってそれが嫌。それであればprotobufで定義を書いておいてそれを各言語向けにコード生成して使えば良いのではないか、ということで作られたというのがこのプラグインです。

同じような悩みがある方、ぜひご利用ください。




今回protoc pluginを書いたのははじめてで、正直なところまったくのnewbieなので世界観を掴むところからのスタートだったのですが、最初の一歩としては@yuguiさんの記事が大変参考になりました。

qiita.com

この記事で基本的なことを把握しつつ、あとは既存のプラグイン実装 (e.g. https://github.com/Fadelis/protoc-gen-java-optional) を見ながら公式ドキュメントを読んで実装していく……というふうに進めたところなんとか形になりました。

Javaでprotocプラグインを書くときは素手でやると結構泥臭い部分 (例えばJavaのbuiltin typeのハンドリングなど) があるので、そのあたりは既にあるフレームワークを部分的に使うと楽で良いです。

github.com

例えばSalesforceが出しているコレなどを活用すると面倒な部分を自分で手書きしなくて大変便利。

あと犯した勘違いとしては「protocのプラグイン (つまりexecutableなjar) だけを配信すれば使えるから、ライブラリとしてMaven Centralとかにアップロードしなくても良い」というものがあり、確かに自前でprotoのoptionを提供しない場合それは正なのですが、仮に独自のoptionを提供している場合はそのoptionの情報をJavaのコードとして提供する必要があります。従ってMaven Centralなりなんなりでライブラリコードも配信する必要があります。なのでこのプラグインのライブラリもMaven Centralにアップロードされています。

なおGoで似たようなことをしたい時はsrikrsna/protoc-gen-gotagという便利なやつがあって、これはprotoファイルの中にコメントで書いておくと良い感じで生成コード中のGoのカスタムタグに値を埋め込んでくれるというナイスなやつです。
当初はJavaでもこういうことをやろうと意気込んでいたのですが、Javaのprotocのライブラリはprotoファイル中のコメントを読む機能が無かったり *1、Plugin Insertion Point (つまり生成コードを捻じ込める箇所) が限られていたり *2、仮に protoc-gen-gotag と同じアプローチを採ってprotocによって生成されたコードを構文解析してそこに値をブチ込むにしてもJavaだと色々と大変そうだなあ……などなど色々と制限があったので今回書いたプラグインの形に落ち着いた次第です。

一定のインターバル期間中に stdin に何かが来る、あるいは来ない時に任意のコマンドを実行するツール "conk" を書いてた

github.com

これ去年書いたツールなのでいつの話をしておるのかという感じですけれども、一定のインターバルの間に stdin 経由で何かデータが来た時あるいは来なかった時に任意のコマンドを実行するツールを書いていました。

使い方の例としては

$ ./do_something | conk --interval-sec 5 \
  --on-notified-cmd '["echo", "notified"]' \
  --on-not-notified-cmd '["echo", "not notified"]' \
  --on-ticked-cmd '["echo", "ticked"]'

という風にしてあげると、5秒周期で評価が走り、その周期内に do_something からパイプ経由で何かデータを渡された時には echo notified が、何も来なかった時は echo "not notified" が実行され、そして来ようが来まいが echo ticked は毎度実行されるという挙動をするようになっております。
README のビデオを見てもらうとまさにそのような挙動を確認することができます。


たまにこういうのが欲しくなるので書いた、みたいな感じでした。

openapi-generator を使って go のファイルを生成した時に出てくる `var _ context.Context` の謎

openapi-generator を使って go のコード生成をすると、

// Linger please
var (
	_ context.Context
)

という一見不要そうな変数宣言がコード中に出てきます。これはなんなのでしょうか? 「残しておいてね」とは書いてありますが……

TL;DR

たぶんこれ過去のワークアラウンドがそのまま残っている感じだと思われます。まあ特に気にする必要もなさそう……

<追記>
このワークアラウンドの残滓を消すパッチを送ってみた。
github.com

^ 送ってみたところマージされたのでこの謎は過去のものになりつつあります。
</追記>

詳しく見てみる

というわけでコードを掘ってみましょう。リビジョンについては今日 (2022-02-16) 現在の最新である 986446c1d5e7b2c16c667ed18b6c95b2679268f5 から遡っていきます。

なるほど、ここに該当するコードがありますね。blameしていきましょう。

openapi-generator/modules/openapi-generator/src/main/resources/go/api.mustache の一番古いコミットを見てみるとここになるようです:

ここにもこの変数宣言はまだありますね。つまりこれが最古のコミットで、そこに「ある」ってことは「ある」んだよ、ガタガタ言うな、ということでしょうか……
とまあそうではなく、というのもこのファイルは openapi-generator/modules/swagger-codegen/src/main/resources/go/api.mustache から改名されているので一見すると歴史が途絶えてしまっているようですが、これよりも古いコミットはちゃんとあります。更にこのファイルの歴史を辿ってみましょう。
しかし swagger から openapi に名前が変わっているのは歴史を感じますね。

で、掘っていくとこのコミットに行き当たります: 3ed1aa8e79687bed63dfa064de27317273080471

この diff を見ると、件の変数宣言はここが始祖のようです。それはそうとして golang.org/x/net/context が使われていたりしてなんだか懐しいですね。

で、このテンプレートファイルを読んでみると {{#hasAuthMethods}}ctx context.Context, {{/hasAuthMethods}} という条件分岐があるのがわかります。
つまり、この条件が false になるとこの部分が欠落し、そうなった時に import "golang.org/x/net/context" があるとコンパイラに未使用の import があると怒られるので、そのワークアラウンドに無名変数 (_ って無名変数って名前で良いんでしたっけ) として context.Context を変数宣言することで import 未使用コンパイルエラーを回避しているように読みとれます。なるほど〜。 *1


さて翻って最新のコードを再び見てみましょう。

このコードを読むと context.Context は常に使われているように見えるし、わざわざ var _ context.Context を付けておく必要もないように見えます。これパッと見不要そうに思えるんですが、今でも残しておく必要あるんですかね? 後でパッチでも投げてみようかしら……

結論

// Linger please
var (
	_ context.Context
)

はかつてのワークアラウンドの名残 (だと思われる)。

*1:とはいえコミットログにその旨は書いていない。コミットログの issue 番号も恐らく前の repository のものなのでどれと紐付いているかわからない、という感じなのであくまでコードを読んだ上での推察です