Julia - retry 機構

環境

  • Julia 1.3.0

retry

ある処理がエラーになってしまったとき、時間を置いて再度実行したらエラーが出ないことはよく有ることです。例えばサーバーにうまく接続できないとか。 そんなときに使うのが retry 機構です。retry 機構を使うことでエラー時の再実行を容易にすることが出来ます。

Julia の retry 機構は以下のようなインターフェースになっています。

retry(f;  delays=ExponentialBackOff(), check=nothing) -> Function

関数 f が実際に実行される処理、delays が再実行する間隔と回数、check はどのエラーによって再実行するかをコントロールするときに指定するもの(っぽい)です。

https://docs.julialang.org/en/v1/base/base/#Base.retry

retry() の返り値は第1引数の関数 f に retry 機構を付与したものと考えればよいと思います。

以下の例は1秒間隔で最大3回関数 f を実行

julia> x = 0
0

julia> function f()
           global x += 1
           if x < 3
               println("x = ", x)
               error()
           else
               return println("Hello World")
           end
       end
f (generic function with 1 method)

julia> retry(f, delays=[1, 1, 1])()
x = 1
x = 2
Hello World

do ... end ブロックを使って以下のようにも書けます。ある処理の一部分に retry 機構を付けたい場合はこちらのほうが便利かもしれません。

julia> retry(delays=[1, 1, 1]) do
           global x += 1
           if x < 3
               println("x = ", x)
               error()
           else
               return println("Hello World")
           end
       end()   # retry の返り値は関数なので最後に `([args...])` を忘れずに
x = 1
x = 2
Hello World

実用的にはこんな感じでしょうか。

retry(delays=[1, 1, 1]) do url
    HTTP.request("GET", url)
end("http://httpbin.org/ip")

Exponential BackOff

待つ間隔を指数関数的に増やす場合は ExponentialBackOff を使います。

ExponentialBackOff(; n=1, first_delay=0.05, max_delay=10.0, factor=5.0, jitter=0.1)

https://docs.julialang.org/en/v1/base/base/#Base.ExponentialBackOff

n が retry する回数で、k回目の retry 時の待ち時間は以下の式で与えられます。

{ \displaystyle
t = \min
\left(
  \text{first_delay} \times \text{factor}^{( (k-1) + \mathrm{uniform}(-\text{jitter},~\text{jitter}) )}, \text{max_delay} \right)
}

ここで {\mathrm{uniform}(a,b)}区間 {[a, b)} の一様乱数 { \displaystyle \mathrm{uniform}(a,b) = (b - a) \times \mathrm{rand}() + a}

AWS でのエラーの再試行とエクスポネンシャルバックオフ - AWS 全般のリファレンス

julia> ExponentialBackOff(n=5, first_delay=0.05, max_delay=10, jitter=0) |> collect
5-element Array{Float64,1}:
  0.05
  0.25
  1.25
  6.25
 10.0 

julia> [0.05 * 5^(i-1) for i in 1:5]
5-element Array{Float64,1}:
  0.05
  0.25
  1.25
  6.25
 31.25

Document の但し書きについて

公式 Document を見ると

Before Julia 1.2 this signature was restricted to f::Function.

という但し書きがあります。ようするに retry の定義が 1.2 より前では

retry(f::Function;  delays=ExponentialBackOff(), check=nothing) -> Function

だったということですが、

Return an anonymous function that calls function f.

とあるのだから、f::Function で一見問題ない気がします。

issue や PR をちゃんと見ていないので正確ではありませんが、おそらく Function-like objects などを取れるようになったということを言いたいのかなと私は思いました。

https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects-1

julia> struct Polynomial{R}
           coeffs::Vector{R}
       end

julia> function (p::Polynomial)(x)
           v = p.coeffs[end]
           for i = (length(p.coeffs)-1):-1:1
               v = v*x + p.coeffs[i]
           end
           return v
       end

julia> (p::Polynomial)() = p(5)

julia> p = Polynomial([1,10,100])
Polynomial{Int64}([1, 10, 100])

# Julia 1.2 未満
julia> retry(p)
ERROR: MethodError: no method matching retry(::Polynomial{Int64})
Closest candidates are:
  retry(::Function; delays, check) at error.jl:198
Stacktrace:
 [1] top-level scope at none:0

# Julia 1.2 以降
julia> retry(p)
#50 (generic function with 1 method)

Julia - ソースコードを読んでみる

gdb を使って Julia のソースコードを追ってみる

環境

gdbソースコードリーディング

gdb で追う場合ソースコードからビルドする必要がある。

$ wget https://github.com/JuliaLang/julia/releases/download/v1.3.0/julia-1.3.0-full.tar.gz
$ tar xvf julia-1.3.0-full.tar.gz
$ cd julia-1.3.0
$ export JULIA_BUILD_MODE=debug
$ make
$ ./julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.3.0 (2019-11-26)
 _/ |\__'_|_|_|\__'_|  |  
|__/                   |

julia> 

新規にターミナルを開いて

$ ps aux | grep julia
arch       14398  3.4  2.4 537996 195032 pts/0   Sl+  20:20   0:00 ./julia

$ gdb -p 14398      # 起動中の Julia の pid に attach

(gdb) b jl_arrayset # 適当なところに breakpoint を入れる
Breakpoint 1 at 0x7f1f71a38e70: file /home/arch/.local/julia/julia-1.3.0/src/array.c, line 568.
(gdb) c
Continuing.

# REPL に戻って適当に x = [1, 2] とか打ってみる

Thread 1 "julia" hit Breakpoint 1, jl_arrayset (a=0x7f1f63aa77f0, rhs=0x7f1f611ee050, i=i@entry=0)
    at /home/arch/.local/julia/julia-1.3.0/src/array.c:568
568    {

(gdb) n
802        return jl_svec_data(t)[i];

(gdb) 
571        if (eltype != (jl_value_t*)jl_any_type) {

(gdb) p jl_any_type
$1 = (jl_datatype_t *) 0x7fd8d9b601d0 <jl_system_image_data+16>

(gdb) ptype jl_datatype_t
type = struct _jl_datatype_t {
    jl_typename_t *name;
    struct _jl_datatype_t *super;
    jl_svec_t *parameters;
    jl_svec_t *types;
    jl_svec_t *names;
    jl_value_t *instance;
    const jl_datatype_layout_t *layout;
    int32_t size;
    int32_t ninitialized;
    uint32_t uid;
    uint8_t abstract;
    uint8_t mutabl;
    uint8_t hasfreetypevars;
    uint8_t isconcretetype;
    uint8_t isdispatchtuple;
    uint8_t isbitstype;
    uint8_t zeroinit;
    uint8_t isinlinealloc;
    uint8_t has_concrete_subtype;
    void *struct_decl;
    void *ditype;
}
 
(gdb) ptype jl_svec_t
type = struct {
    size_t length;
}

(gdb) p jl_any_type->parameters->length
$20 = 0

その時その時にどんな値が入ってきているのかわかって面白い。

gdbptrace: operation not permitted. と出たら以下を打つ

$ sudo su -
# echo 0 > /proc/sys/kernel/yama/ptrace_scope

stackoverflow.com

お試し用の Dockerfile

github.com

Julia - Channel コードサンプル

環境

  • Julia 1.2.0

Golang で Channel を使ってようやく Julia の Channel の動きを理解できた気がする。

capa = 10
c = Channel(capa)

function f(c)
    for i in 1:10
        put!(c, i)
    end
end

function g(c)
    for i in c
        sleep(0.5)
        println("From g: ", i)
    end
    println("Finish")
end

@async f(c)
@async g(c)
close(c)

f:id:goropikarikun:20191119232525g:plain

for i in c
    sleep(0.5)
    println("From g: ", i)
end

の部分は channel が close されるまでずっと回り続ける。

Julia には Go の WaitGroup のようなものはないっぽい。Threads.atomic_add!() などを駆使すれば似たようなことを実現できそうな気はする。

Intro to Relational Databases | Udacity を受講した

Udacity で開講されている Intro to Relational Databases を受講してみた。

www.udacity.com

日々、雰囲気で SQL を書いていて基礎知識がないなぁと感じていたので受講してみた。終了するまでに4時間21分費やした。

講義内容としては SQL の基本的な構文(select, join, create table, view) が主で、すでに知っていることが多かったので新たに勉強になることは少なかったけれども、SQL インジェクションとスクリプトインジェクションの実例を見ることができたのは良かったと思う。

この講義では前回受けた講義同様 Vagrant を使っていたが、私は代わりに Docker を使った。 まだまだ Docker に慣れていないので受講時間よりも Dockerfile を書いている時間のほうが長かったように思う。。。 講義で使われている Vagrantfile を参考に Ubuntu 16.04 イメージを元にした Dockerfile を作ったのだが、後々になって思うと PostgreSQL のコンテナと Python のコンテナを使えばよかったなと思う。

goropikari.hatenablog.com

github.com

ここ最近は Udacity の講義を受講して基本的なことを勉強しているが、もう少し難しい講義を受けてみたいと思うようになってきた。

Configuring Linux Web Servers | Udacity を受講した

Udacity で開講されている Configuring Linux Web Servers を受講してみた。

www.udacity.com

TIMELINE: Approx. 1 Weeks という講義だけあって重い講義ではないので1日でサクッと終わらせた。 講義では Vagrant を使ってそこに Ubuntu Server を建てるというものであったものの、私は Docker を使って受講してみた。 とくに Vagrant でなければいけないということもなかったので Docker でも問題はなかった。

github.com

今まで ufw をとりあえず設定していたものの、恥ずかしながらそれが何であるのかわからず設定していたがこの講義を受けることでようやく設定する意味を理解することができた。

講義では PostgreSQL に関してはインストール方法だけが紹介されていて、接続してその情報を Web ページに表示させる部分は完全に Exercise になっていた。 ネットの情報を参考にしながら無事に接続でき Web ページにもDB内の情報を Web ページに表示することができた。

極めて些細なことであるがフレームワークを使わずに表示させたことがなかったので勉強になった。 Docker の勉強にもなって短いながら有意義な勉強をできたように思う。