Sidekiq v6.0.6 + sidekiq-cron + fakeredis で Redis に接続しようとして CI のテストが失敗する

2020/04/06 追記: この問題は sidekiq-cron v1.2.0 で解消されました👏

2020/04/01 追記: はじめは Sidekiq 単体の問題かと思っていましたが、sidekiq-cron と組み合わせている場合に発生するようなので、全体的に更新しました。

Sidekiq v6.0.6 がリリースされました。

Sidekiq v6.0.6 + sidekiq-cron v1.1.0 + fakeredis の組み合わせで、CircleCI で DB のマイグレーションを行う際にテストが失敗するようになり、対応したメモです。

僕が参加しているプロジェクトでは、RSpec を実行するときは、fakeredis という Redis をシミュレートする Gem を使用しています。

以下が CI のテストが失敗したときのログです。 DB のマイグレーション後、本来は Redis に接続しないはずの処理なのに、127.0.0.1:6379 の Redis に接続できないとエラーが発生しています。

bin/rails db:create db:migrate

# ...

Traceback (most recent call last):

# ...
    26: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq/launcher.rb:103:in `flush_stats'
    25: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq.rb:94:in `redis'
    24: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:61:in `with'
    23: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:61:in `handle_interrupt'
    22: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:64:in `block in with'
    21: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:64:in `handle_interrupt'
    20: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:65:in `block (2 levels) in with'
    19: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq.rb:97:in `block in redis'
    18: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq/launcher.rb:104:in `block in flush_stats'
    17: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:2411:in `pipelined'
    16: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:52:in `synchronize'
    15: from /usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
    14: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:52:in `block in synchronize'
    13: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:2416:in `block in pipelined'
    12: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:162:in `call_pipeline'
    11: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:306:in `with_reconnect'
    10: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:164:in `block in call_pipeline'
     9: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:196:in `call_pipelined'
     8: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:230:in `process'
     7: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:319:in `logging'
     6: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:231:in `block in process'
     5: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:381:in `ensure_connected'
     4: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:105:in `connect'
     3: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:306:in `with_reconnect'
     2: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:106:in `block in connect'
     1: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:343:in `establish_connection'
/home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:362:in `rescue in establish_connection': Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED) (Redis::CannotConnectError)

対応方法

github.com

github.com

上記の Issue によると、Sidekiq の問題ではなく、sidekiq-cron が sidekiq/launcherrequire しているためだ、とあります。 エラーログのバックトレースに sidekiq-cron が出てこなかったので疑っていませんでしたが、試しに Gemfile の sidekiq-cron をコメントアウトすると、問題が発生しませんでした。

従って、暫定対応になりますが、Gemfile では sidekiq-cron を require: false にして、Sidekiq の initializer で sidekiq-cron を require するようにしました。

# Gemfile
gem "sidekiq-cron", require: false
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  # ...

  require "sidekiq-cron"
  schedule_file = "#{Rails.root}/config/schedule.yml"
  Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end

なぜなのか

Ruby インタプリタを終了する際に Sidekiq の統計情報を Redis に書き込む変更が、v6.0.6 で追加されました。

github.com

sidekiq-cron を require すると、内部で sidekiq/launcherrequire されるので、インタプリタを終了する際に上記の処理が実行され、Redis にアクセスできずにエラーになるようです。 fakeredis を使用しておらず、Redis にアクセスできる状態なら、今回の問題は発生しないものと考えられます。

最後に

今回の問題に対する Pull Request も送られているようなので、今後のアップデートで解消されるかもしれません。 その際は追記します。

有償ですが Sidekiq Enterprise に cron 機能があるので、可能ならそれを使う方が安心できるんじゃないかなぁと思う、今日このごろです。

fakeredis は今回あまり関係がありませんでしたが、テストのパフォーマンスが大きく劣化することがなければ、fake ではない本物の Redis を使った方がいいな、と感じました。

GitHub Actions の Ruby プロジェクトで use-ruby-action を使う

GitHub Actions で Ruby を使う場合、何も考えなければ GitHub 謹製の setup-ruby を使うと思います。

github.com

しかし、この setup-ruby、驚くことに 2020/01 上旬時点で使用できる Ruby のバージョンが 2.4 - 2.6 となっています。 setup-ruby の状況や、それ以外の選択肢については、以下の記事で詳しく書かれています。

mstshiwasaki.hatenablog.com

今回 jp_prefecture gem のビルドを GitHub Actions に移行するにあたって、 use-ruby-action というサードパーティ製の Action を使って Ruby のバージョンを指定するようにしました。

github.com

jp_prefecture gem の設定は以下にあります。

jp_prefecture/build.yml at 1f93bc05062dca365956257e55404453497c4154 · chocoby/jp_prefecture · GitHub

Ruby に関する指定を抜粋するとこんな感じ。

jobs:
  matrix:
    # 省略
    strategy:
      matrix:
        ruby:
          - 2.3
          - 2.4
          - 2.5
          - 2.6
          - 2.7

    steps:
      # 省略
      - name: Set up Ruby ${{ matrix.ruby }}
        uses: eregon/use-ruby-action@master
        with:
          ruby-version: ${{ matrix.ruby }}

使い勝手としては、Ruby のバージョンを 2.6 と指定できるので便利です。もちろん 2.6.5 と指定することもできます。 2.6 と指定した場合は、2.6 系の最新のパッチバージョンを使用します。プロジェクトに .ruby-version が存在すれば、そこに記述している Ruby を使用してくれます。

しかし、use-ruby-action がサポートしている以外の Ruby を使いたい場合は、手動で Ruby をビルドする必要があります。 例えば、rubicure gem では Ruby の Docker コンテナを指定し、複数バージョンの Ruby でビルドを行うアプローチを取っているようです。

rubicure/test.yml at b71e872082dfcc53530f90f20670fdad11e1d7a7 · sue445/rubicure · GitHub

GitHub Actions は使われはじめたばかりで、use-ruby-action は選択肢の一つなので、今後の setup-ruby に期待です。

ThinkPad T495s に Manjaro Linux Xfce Edition を入れたら画面がグリッチした

f:id:cho_co_by:20200119224245j:plain
top コマンドを実行した図

環境

  • Lenovo ThinkPad T495s (AMD Ryzen 5 PRO 3500U)
  • Manjaro Linux 18.1.5
  • Xfce 4.14.2

ThinkPad T495s を入手し Manjaro Linux Xfce Edition をインストールしたのですが、画面がグリッチする現象に遭遇しました。 Manjaro Linux というよりは Xfce の問題で、Radeon の GPU が載った Ryzen CPU を搭載している環境で発生するようです。

解決方法

以下のスレッドに解決方法が書かれていました。

xfconf-query を使って vblank_mode というプロパティに xpresent をセットし、再起動することで解決しました。

forum.manjaro.org

xfconf-query -c xfwm4 -p /general/vblank_mode -t string -s "xpresent" --create

以下のフォーラムには、vblank_modeglx を設定すると、NVIDIA の GPU に適した状態に、vpresent は AMD の GPU に適した状態になると書かれています。 正直なところ、両者の違いが分かりません。Xfce 4.14 では glx がデフォルトで適用されるそうです。

forum.xfce.org

果たしてこれが根本的な解決になっているか不明ですが、とりあえず動いているので良しとします。

RubyMine で rbenv でインストールした Ruby が表示されない時の対応

最初にまとめ

  • rbenv で Ruby をインストールしたのに、RubyMine で認識されないことがある
  • rbenv を入れ直しましょう
  • RubyMine を再起動しましょう

以上


Homebrew で入れた rbenv で Ruby 2.6.5 をインストールしたのに、RubyMine の Ruby SDK & Gems にインストールした Ruby が表示されない。 Ruby を入れ直したり、RubyMine を再起動しても変わらず。

最終的に rbenv を入れ直しました。(ruby-build については手を入れていません)

brew uninstall rbenv
brew install rbenv
rbenv install 2.6.5

RubyMine を再起動します。 きっと表示されるはず。

f:id:cho_co_by:20191220114955p:plain

Alacritty というターミナルエミュレーターを試している

iTerm 2 が重いなぁ、と思って調べていたら、Alacritty というターミナルエミュレーターが爆速という記事をみかけたので試している。

github.com

Alacritty の特徴は GPU で描画するため高速、Rust で書かれたクロスプラットフォームなターミナルエミュレーターという認識。

Mac の場合、Homebrew でインストールできる。

brew cask install alacritty

感想

良いところ

  • とにかく描画が速い
  • iTerm 2 は CPU の使用率が高めだったのだが、Alacritty はあまり使っている様子がない。例えば Activity.app で確認すると Avg Energy Impact は iTerm 2 は 3.2 だが Alacritty は 0.27 と低い。
  • 設定を YAML で書ける

微妙なところ

  • 日本語が確定するまで表示されない (Google 日本語入力の問題かも?)
    • f:id:cho_co_by:20200108082537p:plain
  • マウスをダブルクリックした時の選択単位が Terminal.app や iTerm 2 と異なる
    • aaa@bbb という文字列があるとして、bbb 部分をダブルクリックすると iTerm 2 は bbb が選択されるが、Alacritty は aaa@bbb が選択される
    • (2020/01/15 追記) selectionsemantic_escape_chars@ を追加することで選択する単位を変更できた
  • ダブルクリックで文字列をコピーできない
    • (2020/01/15 追記) selectionsave_to_clipboardtrue に変更することでコピーできるようになった

とにかく描画が速いのが素晴らしく、微妙なところのほとんどは慣れれば気にならなさそう。 タブが無い件については tmux を使っているので問題にならなかった。

設定周り

設定を YAML で書けるところがよい。設定は以下に置いている。

dotfiles/.alacritty.yml at master · chocoby/dotfiles · GitHub

また、設定ファイルのサンプルは Alacritty の Releases にある。

今のところ、フォント以外で手をいれているのはショートカットくらい。

key_bindings:
  - { key: Return,  mods: Command,        action: ToggleFullscreen }
  - { key: N,       mods: Command,        action: SpawnNewInstance }
  • Command + Enter: フルスクリーンを切り替える
  • Command + N: 新しいウィンドウを開く
    • たまに tmux の session を複数開きたくなるので

何かあったら追記するかも。 しばらくは Alacritty を試してみようと思う。

Active Job の deserialize では default_scope は適用されない

Active Job の deserialize では、モデルの default_scope は適用されないという罠にハマったのでメモ。

バージョン情報

  • Rails v6.0.2.1
  • globalid v0.4.2

概要

例えば、以下のモデルがあるとします。 default_scope には、status:active のレコードのみ取得する条件を指定しています。

class Post < ApplicationRecord
  default_scope { where(status: :active) }
end

モデルのオブジェクトを Active Job のキューに追加します。オブジェクトは gid://foo-bar/Post/123 のような Global ID に serialize されて、キューに保存されます。

NotifyPostJob.perform_later(post)

そして、キューが実行されるまでに status:inactive など他の値が入ったものとします。

このキューが Active Job で実行されて、キューに含まれる Global ID からモデルのオブジェクトを deserialize する時に、default_scope は効きません。 僕は ActiveJob::DeserializationError が発生するものだと思っていました。

対応方法

deserialize 時に default_scope を考慮するパターンと、Job 内で考慮するパターンがあると思います。

deserialize 時に考慮するパターン

globalid gem のコードを確認すると、デフォルトでは Global ID からレコードを取得する際に UnscopedLocator という Locator が使用されます。 これは名前の通り、scope を解除してレコードを取得する処理になっています。

        def locate(gid)
          unscoped(gid.model_class) { super }
        end

        private
          # 省略

          def unscoped(model_class)
            if model_class.respond_to?(:unscoped)
              model_class.unscoped { yield }
            else
              yield
            end
          end

https://github.com/rails/globalid/blob/v0.4.2/lib/global_id/locator.rb#L153-L171

UnscopedLocatorBaseLocator を継承しています。

BaseLocator は、シンプルにモデルの find メソッドを呼び出す処理になっています。 この Locator では、default_scope が考慮されます。

        def locate(gid)
          gid.model_class.find gid.model_id
        end

https://github.com/rails/globalid/blob/v0.4.2/lib/global_id/locator.rb#L128-L151

従って、deserialize の挙動を変えるのであれば、UnscopedLocator ではなく BaseLocator を使用するように変更します。

config/initializers/globalid.rb:

# "foo-app" はアプリケーション名
GlobalID::Locator.use "foo-app", GlobalID::Locator::BaseLocator.new

Job で考慮するパターン

もう一つは default_scope を頼るのではなく、Job 内で処理するべきオブジェクトかを確認するパターンです。

  def perform(post)
    return unless post.active?

    # 処理
  end

どちらを採用するかは結構悩みましたが、特別なことがない限り default_scope は適用してほしいので、Locator を変更する方法で対応しました。

経緯

経緯を調べたところ、2015/08 時点では default_scope は考慮されていたようです。

github.com

2016/01 に UnscopedLocator がデフォルトの Locator になったようです。

github.com

github.com

なんだか副作用な気もしますが... 特に Issue が作られていないところを見ると、特に困っている人はいないのか、そもそも使っている人が少ないのか...

Apollon Audio NC800 SL の電源スイッチを取り替えた

Apollon Audio NC800 SL という Hypex NCore NC500 が載っているパワーアンプを購入しました。

www.apollonaudio.com

ところが、電源スイッチがオンのまま戻らないという、ちゃんと動作確認しているのかと不安になる問題が起きました。 メーカーに問い合わせたところ、新しい電源スイッチを送ってもらうことになったので、それを取り替えた記録です。

以下が問題のスイッチです。押したら電源がオンになり、もう一度押したら電源がオフになる方式のスイッチです。 初めて電源を入れた時に押し込めなくなり、それ以来電源がオフにならない、という状態になっています。(アンプ裏の主電源スイッチで電源を操作することは可能)

f:id:cho_co_by:20191006131457j:plain

送ってもらったスイッチ。

f:id:cho_co_by:20191006131705j:plain

天板のネジを外します。

f:id:cho_co_by:20191006131958j:plain

スイッチのコネクターを取り外します。

f:id:cho_co_by:20191006132442j:plain

ナットを緩めるとスイッチを取り外せます。

f:id:cho_co_by:20191006132600j:plain

左が新しいスイッチ、右が古いスイッチ。 どちらも電源をオンにした状態ですが、古いスイッチのほうが深くまで押し込まれています。

f:id:cho_co_by:20191006132806j:plain

新しいスイッチを取り付けました。取り付けは取り外しと逆の手順です。

f:id:cho_co_by:20191006133038j:plain

動作確認。電源オフの状態。

f:id:cho_co_by:20191006133216j:plain

電源オンの状態。良さそうです。

f:id:cho_co_by:20191006133228j:plain

まとめ

このアンプについての感想は後で書くかもしれないし書かないかもしれません。 一言でいうと、アンプモジュールに Hypex NCore NC500 を使っているので音質は問題なさそうだけど、品質やサポートについては期待できないな、という感じです。

このスイッチが届くまでにも色々あって、「スイッチを送ったよ!5 -7 日ぐらいで届くと思うから、届いたら詳しい手順を送るから教えてね」 → 届いたのが 20 日後。追跡番号を確認してみたら発送されたのはそのメールから 10 日後だった。届いてからも「スイッチが届いたよ」と送ったら「手順で分からないことがあったらメールしてね!」だけ返ってきた。手順が必要ないほど簡単だったのでいいのですが...

アンプ本体が送られてきたときも追跡番号が間違っていて、到着するまで追跡できなかった、ということもありました。 海外ガレージメーカーに期待するのが間違いな気もします。

ギャラリー

天板を開けたついでに色々撮ったのでアップ。

f:id:cho_co_by:20191006134530j:plain
中身は結構きれいに見えますがどうでしょう

f:id:cho_co_by:20191006134625j:plain
天板のネジのナットはレール上で動かせるようになっていました。

f:id:cho_co_by:20191006135005j:plain
Hypex NCore NC500 のアンプモジュール

f:id:cho_co_by:20191006135217j:plain
インプットバッファーボード。オペアンプには「Sparkos SS3602」か「Sonic Imagery 994」を選べます。温度感があるという「Sparkos SS3602」にしました。