├── .gitmodules ├── README.md ├── package.reload-scm-1.rockspec ├── package └── reload.lua └── rockspecs └── package-reload-scm-1.rockspec /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlibs/package-reload/7841600687ded1533bb097581a825098292a04ec/.gitmodules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | # Hot code reloader for Tarantool 1.6+ 7 | 8 | ## Overview 9 | 10 | `package.reload` is a Lua package for hot-reloading Tarantool packages. It may come in handy if you have a large in-memory dataset and need to often deploy code changes. 11 | 12 | ## How it works 13 | 14 | When first loaded, `package.reload` lists all the currently loaded packages **without reloading them**: 15 | 16 | tarantool> require('package.reload') 17 | 1st load. loaded: fiber, ffi, io, http.client.driver, console, digest, json, uri, jit.dis_x64, box.internal.gc, crypto, net.box, internal.argparse, jit.bcsave, jit.opt, uuid, fio, pwd, internal.trigger, jit.p, jit.vmdef, os, string, debug, jit.profile, socket, tap, coroutine, net.box.lib, jit.dump, pickle, msgpack, jit.dis_x86, box.backup, jit, jit.v, buffer, box, yaml, xlog, errno, bit, box.internal, jit.zone, package, msgpackffi, csv, jit.bc, help.en_US, title, box.internal.session, tarantool, strict, fun, table.new, math, help, table.clear, _G, http.client, jit.util, log, table, clock, iconv 18 | --- 19 | ... 20 | 21 | On subsequent calls, `package.reload` reloads all the packages that have been loaded since the previous call to `package.reload`. Suppose you have loaded three packages: `avro_schema`, `expirationd`, and `memcached`. Then after calling `package.reload` you should see a similar output: 22 | 23 | tarantool> package.reload() 24 | 2nd load. Unloading {memcached, expirationd, avro_schema.compiler, avro_schema.runtime, avro_schema.il, avro_schema, avro_schema.backend, avro_schema.frontend} 25 | reload:cleanup... 26 | reload:cleanup finished 27 | --- 28 | ... 29 | -------------------------------------------------------------------------------- /package.reload-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'package.reload' 2 | version = 'scm-1' 3 | source = { 4 | url = 'git+https://github.com/moonlibs/package-reload.git', 5 | branch = 'master', 6 | } 7 | description = { 8 | summary = "Module for unloading previously loaded modules", 9 | homepage = 'https://github.com/moonlibs/package-reload.git', 10 | license = 'BSD', 11 | } 12 | dependencies = { 13 | 'lua >= 5.1' 14 | } 15 | build = { 16 | type = 'builtin', 17 | modules = { 18 | ['package.reload'] = 'package/reload.lua' 19 | } 20 | } 21 | 22 | -- vim: syntax=lua 23 | -------------------------------------------------------------------------------- /package/reload.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | -- usage -- 4 | 5 | package.reload:register(object) -> object:destroy() 6 | package.reload:register(cb) -> cb() 7 | 8 | package.reload:register(cb,...) -> cb(...) 9 | package.reload:register(ref,cb) -> cb(ref) 10 | 11 | ]] 12 | 13 | local M 14 | local N = ... 15 | local fiber = require 'fiber' 16 | local log = require 'log' 17 | 18 | local function numstr( n ) 19 | if n == 1 then return '1st' 20 | elseif n == 2 then return '2nd' 21 | elseif n == 3 then return '3rd' 22 | else return tostring(n)..'th' 23 | end 24 | end 25 | 26 | package.loaded[N] = nil 27 | 28 | if not package.reload then 29 | 30 | local fio = require('fio'); 31 | local src = debug.getinfo(3, "S").source:sub(2); 32 | 33 | -- Keep original file path, even if it was a symlink 34 | --local lnk = fio.readlink(src); 35 | --if lnk then src = lnk end; 36 | 37 | M = setmetatable({ 38 | O = {}; 39 | F = {}; 40 | C = {}; 41 | loaded = {}; 42 | count = 1; 43 | script = src; 44 | started = os.date("%Y-%m-%dT%H:%M:%S"); 45 | 46 | -- spin = 1; -- id of currently starting generation 47 | -- turn = 0; -- id of successfully loaded generation 48 | -- roll step ring 49 | 50 | },{ 51 | __tostring = function () return 'package.reload{}' end; 52 | __call = function(m,...) 53 | if type(box.cfg) ~= 'function' then box.session.su('admin') end 54 | dofile(M.script) 55 | end; 56 | }) 57 | local loaded = {} 58 | for m in pairs(package.loaded) do 59 | M.loaded[m] = package.loaded[m] 60 | table.insert(loaded,m) 61 | end 62 | log.info("1st load. loaded: %s",table.concat(loaded, ", ")) 63 | package.reload = M 64 | else 65 | M = package.reload 66 | M:_reload() 67 | end 68 | 69 | function M:wait_start() 70 | if self._wait_start == nil then 71 | self._wait_start = fiber.channel(1) 72 | elseif self._wait_start then 73 | --- 74 | else 75 | return 76 | end 77 | return self._wait_start:get() 78 | end 79 | 80 | function M:_reload() 81 | M.count = M.count + 1 82 | local unload = {} 83 | for m in pairs(package.loaded) do 84 | if not M.loaded[m] then 85 | table.insert(unload,m) 86 | package.loaded[m] = nil 87 | end 88 | end 89 | log.info("%s load. Unloading {%s}",numstr(M.count),table.concat(unload, ", ")) 90 | M.reloaded = os.date("%Y-%m-%dT%H:%M:%S"); 91 | M:cleanup() 92 | end 93 | 94 | function M:cleanup() 95 | log.info("%s:cleanup...",N) 96 | if self.main then 97 | package.loaded[self.main] = nil 98 | end 99 | collectgarbage() 100 | for ref,cb in pairs(self.O) do 101 | cb(ref) 102 | self.O[ref] = nil 103 | end 104 | for f in pairs(self.F) do 105 | f() 106 | self.F[f] = nil 107 | end 108 | for t in pairs(self.C) do 109 | local cb = t[1] 110 | table.remove(t,1) 111 | cb(unpack(t)) 112 | self.C[t] = nil 113 | end 114 | log.info("%s:cleanup finished",N) 115 | collectgarbage() 116 | end 117 | 118 | M.deregister = M.cleanup 119 | 120 | function M:register(...) 121 | assert( self == M, "Static call" ) 122 | if select('#',...) == 1 then 123 | local arg = ... 124 | if type(arg) == 'table' then 125 | if arg.destroy then 126 | self.O[ arg ] = arg.destroy 127 | else 128 | error("One arg call with object, but have no destroy method") 129 | end 130 | elseif type(arg) == 'function' then 131 | self.F[arg] = arg 132 | else 133 | error("One arg call with unsupported type: ",type(arg)) 134 | end 135 | else 136 | local arg1 = ... 137 | if type(arg1) == 'function' then 138 | local t = {...} 139 | self.C[t] = t 140 | elseif select('#',...) == 2 then 141 | local ref,cb = ... 142 | if type(cb) == 'function' then 143 | self.O[ref] = cb 144 | else 145 | error("Bad arguments") 146 | end 147 | else 148 | error("Bad arguments: ", ...) 149 | end 150 | end 151 | end 152 | local yaml = require 'yaml' 153 | fiber.create(function() 154 | local x = 0 155 | repeat 156 | -- log.error("Cleanup wait...") 157 | fiber.sleep(x/1e5) 158 | x = x + 1 159 | until type(box.cfg) ~= 'function' and box.info.status == 'running' 160 | require('log').error("Cleanup %s, %s", x, box.info.status) 161 | package.loaded[N] = nil 162 | if M._wait_start then 163 | while M._wait_start:has_readers() do 164 | M._wait_start:put(true) 165 | end 166 | M._wait_start = false 167 | end 168 | return 169 | end) 170 | return M 171 | -------------------------------------------------------------------------------- /rockspecs/package-reload-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'package-reload' 2 | version = 'scm-1' 3 | source = { 4 | url = 'git+https://github.com/moonlibs/package-reload.git', 5 | branch = 'master', 6 | } 7 | description = { 8 | summary = "Module for unloading previously loaded modules", 9 | homepage = 'https://github.com/moonlibs/package-reload.git', 10 | license = 'Artistic', 11 | } 12 | dependencies = { 13 | 'lua ~> 5.1' 14 | } 15 | build = { 16 | type = 'builtin', 17 | modules = { 18 | ['package.reload'] = 'package/reload.lua' 19 | } 20 | } 21 | 22 | -- vim: syntax=lua 23 | --------------------------------------------------------------------------------