matarilloの雑記

GitHubに公開したソフトウェアなどについて書きます。

Language Server Protocol (ライブラリのC#実装) のバージョンアップ作業中

Language Server Protocolを放置している間にプロトコル仕様はバージョンが上がってるし、ライブラリーは 榎本さんが使ってみているようですし(嬉しい1)、プロトコルの最新仕様に追従した上で、サーバー実装サンプルも更新したい2と思っています。

ただし、いろいろ破壊的変更を入れたくなってきたので、えいやっとメジャーバージョンを上げることにします。

次バージョンの破壊的変更はGitHubのWikiにまとめました


  1. もし他言語に流れることになるとしても……

  2. というのも、今のサンプルはvscode-languageserver-node-exampleからのフォークなんですが、そっちはobsoleteになってて、vscode-extension-samplesに吸収されてるんですね。移行後の方では VSCode Language Server - Client Module の依存バージョンも上がっているので、どうにかしたいわけです。ちなみに、今のサーバー実装サンプルは Classic .NET / Mono 用なのですが、vscode-extension-samplesからフォークしなおした版は.NET Core用として作ってます。いずれちゃんと公開すると思います。

SharpDevelop 5.1の日本語リソース(修正版)を公開します

SharpDevelopMonoDevelopやXamarin Studioのフォーク元であるOSSの.NET IDEです。 Visual Studio ExpressやCommunityも存在する今、あえてSharpDevelopを使う人というのはきっと何らかのポリシーや特殊事情がある方だと思います。

そんなSharpDevelopの最新バージョンは5.1(2016/4/14リリース)ですが、残念ながら日本語リソースが壊れており、UIを日本語に設定すると多くの表示が「??????」などになってしまいます。これでは困りますので、過去の日本語リソースを参照しつつ、一部は追加して、作り直してみました。

ソース: https://github.com/matarillo/SharpDevelop/blob/master/data/resources/StringResources.jp.resx

バイナリ(.resources): https://github.com/matarillo/SharpDevelop/releases/tag/5.1.0

プルリクエストも出してみたのですが、いつ取り込まれるかはわからないので、それまでのつなぎにどうぞ。

(追記)SharpDevelop 5.1でも、プロジェクトファイルはVisual Studio 2010のもの( ToolsVersion="4.0" )までしかサポートしていないんですね。これじゃ今使うにはちょっとあれだな。

C#で書いたHTMLパーサー dcsoup を.NET Standard対応させました

ほぼタイトルオンリーですが、dcsoup 1.0.0をリリースしました。

github/dcsoup

nuget/dcsoup

.NET Standard 1.3対応済みです。

とはいえ、dcsoupを使うぐらいならAngleSharpを使うのがおすすめです……

Language Server Protocolのサーバー側ライブラリを書きました

.NETの話です。タイトルには書かずに本文の一番上に書きました。

Language Server Protocolについての説明はatsushienoさんの記事1vvakameさんのセッションスライドを読んでもらうことにして、ここでは省略します。

language serverとは、IDEが必要とするプログラムのプロジェクト ソースを解析して情報を提供する機能を、サービスとして実現するものです。

ほんとに作りたいのはNemerleの言語サービスなんですが、そこに行く前に準備として.NETのライブラリをでっち上げました。

https://github.com/matarillo/LanguageServerProtocol

Nugetにも登録しました。 https://www.nuget.org/packages/LanguageServerProtocol/

使い方

Microsoftが公開している、nodeによる実装サンプル https://github.com/Microsoft/vscode-languageserver-node-example をフォークして、 サーバー側を拙作LanguageServerProtocolに置き換えたもの https://github.com/matarillo/vscode-languageserver-csharp-example を見たほうがいいと思います。

概要だけ書くとこんな感じです。

  • LanguageServer.ServiceConnectionクラスを継承したクラスを作ります。
  • クライアント(≒エディタやIDE)からサーバーに送られてくるメッセージを処理するには、virtualメソッドをオーバーライドします。
  • サーバーからクライアントにメッセージを送るには、ServiceConnection.Proxyプロパティからたどることができる各種メソッドを呼び出します。
  • クライアントからの通信を待ち受けるには、ServiceConnection.Listen()メソッドを呼び出します。

  1. https://atsushieno.github.io/exqiita/2016/08/19/ にもありますが、本文には一応Qiitaの方を載せました。

ProtocolReaderというクラスを作りました

.NETの話です。タイトルに書かずに、本文の一番上に書きました。

通信プロトコルの実装みたいなのを自分で書くこと、たまにはありますよね。常にHTTPなりgRPCなりを使うわけではないのですから。

で、そういうプロトコル、テキストとバイナリが混じってたりするじゃないですか。

HTTPで画像をGETするときを例にすると、ヘッダー部はASCIIテキスト(\r\n区切り)で、その中にボディ部のサイズが含まれてて、ボディ部はバイナリ、って感じですよね。

f:id:matarillo:20170610032839p:plain

こういうのを自分で書くときに、BinaryReaderが使えそうかなと思うわけですが、区切り文字を探すときにちまちま1バイトずつ読むの、面倒じゃないですか。

かと言って、TextReaderみたいなクラスを使ってしまうと、これが内部でバッファリングしてたりとかするので、元のストリームからはボディ部のバイナリを読めなかったりするわけじゃないですか。

あと、プロトコルを理解せずにうかつに多くのバイトを読み取ろうとすると、データが送られてきてないので待つことになったりするじゃないですか。

そういうのが面倒なのでひとつクラスを書きました。Matarillo.IO.ProtocolReaderクラスです。

https://github.com/matarillo/ProtocolReader

Nugetにも登録してありますが、実体はクラス1個だけなので、ソースだけ流用して適当に使ってもらって構いません。(MITライセンスにしてあります。)

使い方

public async Task<byte[]> ReadToSeparatorAsync(byte[] separator)

separatorで指定したセパレーターが現れるまで現在のストリームを読み進め、セパレーター直前までのバイト列を非同期的に返します。 イメージはTextReader.ReadLineAsyncメソッドです。なのでセパレーターとして\r\nを渡したとき、返されるバイト列には\r\nは含みません。

public async Task<byte[]> ReadBytesAsync(int count)

countで指定した長さまで現在のストリームを読み進め、count長のバイト列を非同期的に返します。 イメージはBinaryReader.ReadBytesの非同期版です。

どちらのメソッドも、ストリームが閉じてしまった場合はそこまでのバイト列しか返しませんので、その点は注意です。

C# (ASP.NET Core) でもisuconやってみたい

えーと今日はもう12/28ですが、この記事は .NET Core Advent Calendar 2016の10日目の記事 兼 ASP.NET Advent Calendar 2016 の17日目の記事だということにさせてください。。。

isuconというのは「いい感じにスピードアップコンテスト」の略です。公式ブログによれば

ISUCONとは

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。過去の実績も所属している会社も全く関係ない、結果が全てのガチンコバトルです。

というものです。雑に捕捉すると、既定の時間内に、与えられたサーバー、ミドルウェア、アプリケーションをチューニングして、時には(レギュレーションの範囲内で)全とっかえして、ベンチマークのスコアを上げるという大会です。

アプリケーションは運営側が参考実装したものが提示されます。直近の大会(isucon6)のオンライン予選では、Perl/Ruby/Python/PHP/Node.js/Go/Scalaでの実装が提示されていましたが、実際に参加したチームを見るとC++やJavaで再実装したチームがあったりするわけです。

まあそんな感じで楽しそうなので、.NET Coreで参加できないかなーなんて思ったりするわけです。

過去問はgithubで公開されている上、matsuuさんがいろんな環境用にportしています。とりあえず今回はisucon4の予選問題でちょっと試してみました……が、なかなか時間がかかりました。

matsuuさんが作成されたAnsible Playbookをベースにしたのですが、こちらはCentOS6ベース。ところが.NET CoreはCentOS7.1以降をサポートとあります。「と言ってもCentOS6でビルドすれば動くんじゃ?」と思って試してみたら、まあそんな簡単なものではなく。結局あきらめてCentOS7ベースにしました*1。その際に、ちょっとうまく動かなかったところがあったので、フォークして雑に上書きしました

インストールには CentOS 7 Minimal ISOを使いました。

rootでログインして、以下、雑に上書きした https://github.com/matarillo/ansible-isucon を動かします。

$ yum install -y epel-release git
$ yum install -y ansible
$ git clone https://github.com/matarillo/ansible-isucon.git
$ cd ansible-isucon/isucon4-qualifier
$ ansible-playbook -i local playbook.yml

もし、途中の 02_supervisord.yml あたりで Could not find the requested service "'supervisord'": といったエラーが出てたら、systemctl daemon-reloadを実行した上で、ansible-playbookを再実行してみてください。(追記: ansible側を修正して対応しました。)

さて、このAnsible Playbookを動かすと、isucon4の公式リポジトリをフォークして雑にASP.NET MVC Coreアプリケーションを追加した https://github.com/matarillo/isucon4 が組み込まれます……が、まだ中途半端な出来です。

  • .NET Core SDK 1.0 Preview 4 build 004233 を手で入れてください (追記: ansible側を修正して対応しました。)
  • .NET Coreアプリケーションを手で起動してください

前者はもう単純に手抜きです。時間があったら対応したいです。(追記: ansible側を修正して対応しました。)後者は、公式の仕組み (supervisord) でサービス化しようとしたのですが、残念ながら500エラーが解消できませんでした。こちらも対応したいですが、正式版リリースとどっちが早いかな…

ともあれ、.NET Coreアプリケーションを動かすには、ISUCON4 予選当日マニュアル を参考に、

  • supervisord を止める
  • /etc/supervisord.conf を修正して、すべてのアプリケーションautostart=false にする
  • supervisord を再開する

とした後*2

$ cd webapp/csharp
$ dotnet restore
$ dotnet run

でアプリケーションを動かします。

動いたかどうかはブラウザからポート80*3にアクセスするのが一番いいのですが、CentOS 7の初期状態ではファイヤーウォールがポート80をブロックしているはずなので、『CentOS7のfirewalldをまじめに使うはじめの一歩(systemdも少し)』 などを参考にポートを開けましょう。

さて、この状態で動いているアプリケーションですが、公式の参考実装を移植した体になっているので、チューニングは一切されていません。

https://github.com/matarillo/isucon4/tree/master/qualifier/webapp/csharp

  • Controllers
    • HomeController.cs ... Index, Login, MyPage, Reportの4つのアクションを実装。ロジック自体は Logicクラスに移譲
  • Infrastructure
    • IDbFactory.cs ... DbConnectionのファクトリーインターフェース
    • MySqlDbFactory.cs ... MySql.Data に依存した実装
  • Models
    • Logic.cs ... アプリケーションロジックの本体。データベースアクセスはDapperを利用。
    • LoginLog.cs ... login_log テーブルの行に対応するクラス
    • Threshold.cs ... 環境変数で与えられる「ユーザーがロックされるログイン失敗回数」「IPアドレスがBANされるログイン失敗回数」に対応するクラス
    • User.cs ... users テーブルの行に対応するクラス
  • Views
    • Home ... Index, MyPageのcshtmlテンプレート
    • Shared ... テンプレートの共通レイアウト
  • Program.cs ... アプリケーションサーバー設定(png/cssの位置指定、ポート8080の利用)
  • Startup.cs ... ASP.MVC Coreのルーティング、DIの設定など

私の環境でベンチマークを動かしてみたら、こんな感じでした。

csharp

Pythonの参考実装だとこんな感じ。

python

ほぼ差がありません。

アプリケーションに手を入れるとしたら、データアクセスが中心でしょうね。

*1:6と7ではいろんなところの仕組みが違うのですが、無視しました

*2:もしアプリケーションのプロセスがまだ生きていたら killやpkillで強制終了させる

*3:リバースプロキシとしてnginxが動いている