rails activejob vs sidekiq

WIP

最初一直不喜欢rails里的activejob

原因很简单, 日志里它的日志很难懂

后来花时间读了一下文档, 了解到了gid这个概念, 知道了它里面有一些和controller里一致的rescue_from等等功能, 觉得它确实有优势

日志也能看懂了, 不那么排斥它了

甚至因为gid的缘故, 有点喜欢这个设计

但是, sidekiq里提倡的最佳实践是: 使用简单参数, 很清晰易懂, 用gid其实也做到了一样的事, 而且他可以传递model实例, 挺好的优势

直到昨天, 看到同事的一个pr修改了activejob的参数(由model实例改为id), 我提出是不是没必要这样, 因为前面说到的原因

但是他展示给我 如果在log里搜索 “Sidekiq::Extensions::DelayedClass ARGS” 会发现, 虽然perform接口的model实例会被序列化为gid, 但是这种延时的任务, 实际上还是序列化为了yml

所有model实例的所有信息都被存储了下来, 然后做反序列化, 所有信息都被记入了log里面, 太糟糕了

  • 反思.1: sidekiq的代码没读全, activejob的代码没读全, 重大的疏漏
  • 反思.2: 简单的是好的, 简单的最佳实践也需要认真了解其背后的逻辑
  • 反思.3: 不要对自己信奉的东西有太强的自信, 多多沟通和交流, 谦虚的向大家学习

vagrant booting error

Error.1 NS_ERROR_FAILURE

Using macos to debug c program is annoying…

Tried to use vagrant vm, but I met error while booting the vm

1
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component MachineWrap, interface IMachine

Solution

https://stackoverflow.com/questions/52689672/virtualbox-ns-error-failure-0x80004005-macos

I tried reinstall Virtualbox, not working

I tried reboot mbp, this time it worked

Don’t know the root reason yet

Error.2 umount: /mnt: not mounted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ vagrant up

...

The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

umount /mnt

Stdout from the command:



Stderr from the command:

umount: /mnt: not mounted

then I can vagrant ssh into the machine, seem above error is not a big deal

But I want to make it go away…

Reason

seems related to vagrant plugin vagrant-vbguest

1
2
vagrant plugin list
vagrant plugin uninstall vagrant-vbguest

Solution

Unistall the plugin and reboot the vagrant vm

1
2
3
4
vagrant halt
vagrant destroy
vagrant up
vagrant ssh

Error.3 Remote connection disconnect. Retrying and ask for ssh password

https://github.com/puphpet/puphpet/issues/1253#issuecomment-145429092

for some dumb people like me, do not use /home/vagrant as your shared folder (in your vm), because the .ssh files are not accesible.

don’t understand the reason, but this is the cause, remove the config.vm.synced_folder resolved the problem

How to debug

1
2
vagrant ssh-config
vagrant ssh --debug

Solution

rm config.vm.synced_folder in Vagrantfile and try again

error note on installing ruby 3.1.2

The error

1
2
3
4
5
6
7
8
$ rvm install 3.1.2

Error running ' CFLAGS=-O3 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include LDFLAGS=-L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib ./configure --prefix=/Users/lijunwei/.rvm/rubies/ruby-3.1.2 --disable-install-doc --enable-shared',
please read /Users/lijunwei/.rvm/log/1660401863_ruby-3.1.2/configure.log
There has been an error while running configure. Halting the installation.

$ cat /Users/lijunwei/.rvm/log/1660401863_ruby-3.1.2/configure.log
__rvm_log_dotted:23: permission denied:

“__rvm_log_dotted:23: permission denied:”

The solution

https://github.com/rvm/rvm/issues/5055#issuecomment-815036429

1
rvm install 3.1.2 --with-gcc=clang

How to read or debug rubocop source code

Actually, this is a general trick to understand any code: Find a concrete question -> get source code -> add breakpoints -> mess around -> observe the stacktrace…

Solid example

rubocop codebase is huge, what if I want to know how it works by observing one cop?

follow the steps bellow:

  1. clone the repo and cd into repo
  2. Run bundle install
  3. add breakpoint in source code, e.g.: add require 'pry'; binding.pry to lib/rubocop/cop/lint/shadowed_exception.rb
  4. find the executable of the gem, in this case exe/rubocop
  5. Run exe/rubocop --only Lint/ShadowedException tmp/shawdowed_exception.rb and mess around with that breakpoint
  6. Run caller.grep /rubocop/ to see stacktrace

btw, go ahead to see spec/tests is also a good option.

Have fun reading source code

20220804 update

use exe/rubocop --cache false --only Lint/ShadowedException tmp/shawdowed_exception.rb to avoid odd behaviors while debugging

Rubocop Inspired Learning

背景

第一次知道”rubocop”这个关键字并且开始用它, 是在一次分享”exception handling”后

当时分享的主题是, ruby里的exception类有的继承关系, 最常被捕获的异常应该是StandError及它的子类, 不该捕获Exception类 参考

最后的结论是, 希望大家了解一下 Exception相关类的继承关系, 捕获该被捕获的异常

分享的最后, 有人提到了rubocop, 我了解了一下, 才意识到, 这件事其实可以不用写代码的人主动去检查和注意, 完全可以交给lint程序自己检查

be rubocop --only Lint/RescueException

问题

系统变复杂后, 代码里几乎不可避免的出现了一大团异常处理的代码

尤其是在代码入口处, 一个个的rescue 看得头大, 有一次还因为没注意到一个更具体(specific)的异常被一个更通用(genral)的异常给遮蔽(shadowed 这个描述是后来知道的, 最初没有这个准确的概念)掉了, 好一阵debug才发现原因

这时候我想这种检查是不是也可以自动完成呢?

思路

问题 -> 需求 -> 自己实现? -> 查查已有工具 -> 找到已有工具 -> 验证 -> 有问题 -> 尝试解决/求助 -> ...

第一反应还是自己写个工具检查

但是多了个心眼, 先查查有没有已有的工具已经做了这件事?

事实证明这个问题早有人遇到过, 并且有了解决办法: be rubocop --only Lint/ShadowedException

既然已经有了工具, 那试着用用呗

结果不理想, 没法识别出restclient里被遮蔽的异常, 试了下看rubocop的代码, 一下看懵了, 暂时放弃

于是换了个更直接的方式: 给rubocop提issue: Lint/ShadowedException cop not working as expected for RestClient::RequestFailed exception

作者很快做了回复:

RuboCop is a static analysis tool, it doesn’t know inheritance relationships at runtime. It has false negatives for unknown inheritance relationships.

这里出现了一个不了解的概念: “static analysis tool”

经过 rubocop -> parser -> ast -> wiki: Abstract syntax tree -> wiki: Program analysis

才算意识到 “Program analysis can be performed without executing the program (static program analysis), during runtime (dynamic program analysis) or in a combination of both.”

  • 在 不执行代码的前提下 检查代码, 这种叫做 静态程序分析
  • 在 执行代码的前提下 检查代码, 这种叫做 动态程序分析

而rubocop是一个静态程序分析工具, RestClient::RequestFailed相关的异常只能在运行时确定(需要 require 'rest-client')

这下算是搞明白了作者回复的含义

学习路径

coding -> problem -> analysis: can be automated? -> tool available?

反思

  • 好的问题 + 兴趣 引向好的结果
  • 不要一上来就想着自己实现, 重复造轮子, 并且做出来的轮子不一定圆
  • 眼界要开阔, 多多求助
  • 动手尝试, 实践
  • 读wiki/manual要仔细一点

接下来的TODO

rubocop 里的 ShadowedException cop, 没法检查出需要运行时才能判断出的继承关系

但是他可以检测出 ruby里已有的一些异常(TODO 验证一下)

处理我的这个需求有两种方式

  1. 自己实现一个新的工具, 专门做这件事

    • 有点重新造轮子, 但是只要能问题就是好的(做好了demo)
  2. 把这个能力集成进ShadowedException里面, 这里涉及到比较难的问题是

    • 需要读懂rubocop的代码
    • rubocop是”static analysis tool”, 加入这种 运行时检查的功能, 似乎和他的定义冲突, 怎么处理这种关系?

ruby-script-encoding

ref: https://ruby-doc.org/core-3.0.0/Encoding.html#class-Encoding-label-Script+encoding

终于知道了ruby script里encoding这个magic comment是什么作用了

这个magic comment不是被编辑器解析的, 而是由ruby解释器解析的(not 100% sure)

All Ruby script code has an associated Encoding which any String literal created in the source code will be associated to.

The default script encoding is Encoding::UTF_8 after v2.0, but it can be changed by a magic comment on the first line of the source code file (or second line, if there is a shebang line on the first).
The comment must contain the word coding or encoding, followed by a colon, space and the Encoding name or alias:

The __ENCODING__ keyword returns the script encoding of the file which the keyword is written:

  • file1.rb

    1
    2
    3
    4
    # encoding: ASCII-8BIT

    p "hello".encoding # #<Encoding:ASCII-8BIT>
    p __ENCODING__ # #<Encoding:ASCII-8BIT>
  • file2.rb

    1
    2
    3
    4
    # encoding: UTF-8

    p "hello".encoding # #<Encoding:UTF-8>
    p __ENCODING__ # #<Encoding:UTF-8>

amg error handling

见识真的很重要, 如果我早点意识到”Error Handing”的正确做法是使用”Error Service”, 可以省下不少时间, 减少很多没有意义的成就感, 获得真正的提高

遇到的问题

在做AMG时, 经常在苦恼错误的处理:

  • 捕获了, 会收不到通知
  • 不捕获, 会老是收到提示
  • 打印出backtrace有那么长一串, 找不到关键信息
  • 而且每次都得查日志
  • 定时任务(whenever)失败了, 除了查日志没办法发现吗?

做过的尝试(浪费过的时间)

  • 在每个重要的rescue里, 增加企业微信机器人的通知
  • 增加error_logs表, 在rescue里记录错误信息, 并且专门给它写了个index页和show页面用于定位问题(当时还洋洋自得…)
  • Exception类增加monkey patch, 在每个rescue里, 打印或者记录e.to_hash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Exception
    def to_hash(request_id = nil)
    hash = {}
    hash[:request_id] = request_id if request_id
    hash[:class] = self.class.to_s
    hash[:message] = self.message
    hash[:backtrace] = self.app_backtrace

    return hash
    end

    def app_backtrace
    return self.backtrace.select {|trace| trace.include?(Rails.root.to_s)}
    end
    end
  • 由于错误信息太多, 做过几轮收敛, 忽略不重要的错误
  • 为sidekiq的错误信息写了个error_handler, 里面打印错误信息和记录error_log, 这步倒问题不大
  • … 记不清了

正确的做法

https://www.mikeperham.com/2013/08/25/please-use-an-error-service/

error handling这个问题的解决方案是: 使用 bugsnag 之类的Error Service

应用里不该捕获所有的unknown exceptions, rescue StandardError => e 甚至 rescue Exception => e 都是不可取的

错误应该被暴露出去, 然后及时修复; 由于错误被吞掉导致的问题很让人恼火, 而且有时候会很难排查

使用 Error Service(以bugsnag为例) 有什么好处呢?

  • 代码只需要处理已知的异常, 会干净一些
  • 意料外的异常可以报告给bugsnag, 可以触发通知, 查看backtrace, error trending…
  • 可以直接在bugsnag上创建bug, 分配给研发团队处理
  • 不用再在日志里grep error

对于难发现定时任务的报错, 其实也有解决方案: 使用sidekiq的插件 sidekiq-scheduler, 放弃linux的cron服务

有什么好处呢?

  1. sidekiq-scheduler 沿用了 sidekiq的web页面, 增加了一个tab, 显示定时任务的状态等信息, 一目了然
  2. 可以在页面上触发定时任务, 不需要登录服务器
  3. 如果出错了, 可以在页面上看到(和失败的sidekiq任务类似), 并且可以利用sidekiq的重试机制

反思

  • 如果我早点理解这些问题的正确解法, 可以少花不少时间在上面; 可惜不知为什么, 竟然没有人提醒我, 大概是因为我只顾埋头自己干, 太少请教其他人
  • 为什么 在我看到 https://www.mikeperham.com/2013/08/25/please-use-an-error-service/ 这篇文章后仍然没有意识到 error service 是什么意思, 能怎么解决我的问题? 大概是我看到了新的名词, 没有主动去做了解, 没有把大佬的话放在心上导致的
  • 为什么sidekiq-scheduler 这样比较成熟的解决方案, 我竟然很久之后才发现? 大概是搜索技能和好奇心的缺失: 如果在 https://rubygems.org/ 上搜索一下 “sidekiq”, 会发现一堆有趣的插件; 如果我真的遇到了项目中的痛点, 要明确自己想要什么, 然后去搜索解决方案, 我遇到的问题, 大概率其他人也会遇到
  • 陷入了”有一把锤子, 看谁都是钉子”的怪圈: 当时的场景中, 太熟悉通过 企业微信机器人 发消息的机制, 没有细想事情的正确做法应该是什么样的

ruby-prepend

最近在项目代码里看到了 prepend 的用法, 读了文档, 基本明白了它的用法; 但是对于说它是为了解决之前 alias_methods 的问题的这部分讨论还没看明白(可能是我没怎么用过alias_method)

在我的理解中这几点比较关键:

  1. 和ruby其他的hook(inherited included…)类似, prepend 也有自己的hook: prepended
  2. prepend把模块里的方法插入到方法查找链里面, 可以覆盖当前类里的方法定义

示例

  • 定义一个普通的类, run方法会遍历并打印参数, 然后返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Service
# perform some real work
def run(args)
args.each do |arg|
puts "arg: #{arg}"
end
{result: "ok"}
end
end

p Service.new.run(["888", "999"])
# arg: 888
# arg: 999
# {:result=>"ok"}
  • 如果给它prepend一个module ServiceDebugger, 里面定义一个同名的方法, 这个模块里的方法定义会覆盖 Service 里的方法定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module ServiceDebugger
def run(args)
puts "Service run start: #{args.inspect}"
end
end

class Service
prepend ServiceDebugger

# perform some real work
def run(args)
args.each do |arg|
puts "arg: #{arg}"
end
{result: "ok"}
end
end

p Service.new.run(["888", "999"])
# Service run start: ["888", "999"]
# nil
  • module ServiceDebugger 里面可以调用super方法, 调用原始定义的方法, 类似 around_hook 里的 yield
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module ServiceDebugger
def run(args)
puts "Service run start: #{args.inspect}"
result = super
puts "Service run finished #{result}"
{new_result: "ok"}
end
end

class Service
prepend ServiceDebugger

# perform some real work
def run(args)
args.each do |arg|
puts "arg: #{arg}"
end
{result: "ok"}
end
end

p Service.new.run(["888", "999"])
# Service run start: ["888", "999"]
# arg: 888
# arg: 999
# Service run finished {:result=>"ok"}
# {:new_result=>"ok"}
  • 可以用这种机制, 在不修改原有代码的情况下, 增强原有的方法, 例如 增加日志; super前的代码类似于 before_hook, 之后的代码类似于after_hook

  • 此外, 可以在被prepend的模块里定义prepended, 里面实现一些自动触发的行为

1
2
3
4
5
6
module ServiceDebugger
def self.prepended(mod)
puts "#{self} prepended to #{mod}"
end
# ...
end

是魔法吗? 不确定; 不知道其他语言有没有这么多hook 和灵活的用法

2022-07-09 09:47:43 update

不是魔法, hook的本质大概是在模块/类的生命周期中 提前定义好”如果存在则会被调用的代码块”, 从外部看来就是hook了

用这种视角来看, 其他语言肯定也可以实现

核心大概是: “生命周期” 和 “封装”

很好例子就是 activerecord 里的 “The Object Life Cycle”

清晰的定义 配合良好的封装, 使得activerecord的hook使用起来很方便

记录 从sublime搜索结果显示为binary引发的思考和行动(WIP)

昨天加今天, 基本搞明白了一个困扰我很久的问题, 有很多收获, 在这里记录一下

最初的问题

  • 有一次在代码库里搜索一个关键字, sublime的结果显示搜索到了, 但是没有preview, 还把那个文件标为了”binary”, 就像这样
  • 当时初步定位到原因是 那个文件里包含了一个 ASCII control characters 里的 BS(backspace), 它在编辑器里渲染的不是正常的字符, 所以能看出它是特殊的, 但是当时对unicode/ascii 和字符编码都了解很少, 不知道是什么意思, 发现把这个字符删掉, 能让搜索结果恢复正常
  • 为什么这种字符让文件的搜索结果变成了binary?
  • 这些个特殊字符是什么?
  • 怎么能快速定位到他们?

学习过程

使用TDD实现这个CharDetector的体会和反思

感觉有价值的的参考资料

PS. 越写越长可不是好习惯!

回答最初问题

WIP