lib/nodo/core.rb in nodo-1.6.5 vs lib/nodo/core.rb in nodo-1.7.0

- old
+ new

@@ -7,70 +7,70 @@ INTERNAL_METHODS = [DEFINE_METHOD, EVALUATE_METHOD, GC_METHOD].freeze LAUNCH_TIMEOUT = 5 ARRAY_CLASS_ATTRIBUTES = %i[dependencies constants scripts].freeze HASH_CLASS_ATTRIBUTES = %i[functions].freeze CLASS_ATTRIBUTES = (ARRAY_CLASS_ATTRIBUTES + HASH_CLASS_ATTRIBUTES).freeze - + @@node_pid = nil @@tmpdir = nil @@mutex = Mutex.new @@exiting = nil - + class << self extend Forwardable - + attr_accessor :class_defined - + def inherited(subclass) CLASS_ATTRIBUTES.each do |attr| subclass.send "#{attr}=", send(attr).dup end end - + def instance @instance ||= new end - + def class_defined? !!class_defined end - + def clsid name || "Class:0x#{object_id.to_s(0x10)}" end - + CLASS_ATTRIBUTES.each do |attr| define_method "#{attr}=" do |value| instance_variable_set :"@#{attr}", value end protected "#{attr}=" end - + ARRAY_CLASS_ATTRIBUTES.each do |attr| define_method "#{attr}" do instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", []) end end - + HASH_CLASS_ATTRIBUTES.each do |attr| define_method "#{attr}" do instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", {}) end end def generate_core_code <<~JS global.nodo = require(#{nodo_js}); - + const socket = process.argv[1]; if (!socket) { process.stderr.write('Socket path is required\\n'); process.exit(1); } - + process.title = `nodo-core ${socket}`; - + const shutdown = () => { nodo.core.close(() => { process.exit(0) }); }; // process.on('SIGINT', shutdown); @@ -90,23 +90,23 @@ #{scripts.map(&:to_js).join} return __nodo_klass__; })() JS end - + protected - + def finalize_context(context_id) proc do if not @@exiting and core = Nodo::Core.instance core.send(:call_js_method, GC_METHOD, context_id) end end end - + private - + def require(*mods) deps = mods.last.is_a?(Hash) ? mods.pop : {} mods = mods.map { |m| [m, m] }.to_h self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) } end @@ -116,95 +116,95 @@ loc = caller_locations(1, 1)[0] source_location = "#{loc.path}:#{loc.lineno}: in `#{name}'" self.functions = functions.merge(name => Function.new(name, _code || code, source_location, timeout, &block)) define_method(name) { |*args| call_js_method(name, args) } end - + def class_function(*methods) singleton_class.def_delegators(:instance, *methods) end def const(name, value) self.constants = constants + [Constant.new(name, value)] end - + def script(code = nil, &block) self.scripts = scripts + [Script.new(code, &block)] end - + def nodo_js - Pathname.new(__FILE__).dirname.join('nodo.js').to_s.to_json + Pathname.new(__FILE__).dirname.join('nodo.cjs').to_s.to_json end - + def reserved_method_name?(name) Nodo::Core.method_defined?(name, false) || Nodo::Core.private_method_defined?(name, false) || name.to_s == DEFINE_METHOD end end - + def initialize raise ClassError, :new if self.class == Nodo::Core @@mutex.synchronize do ensure_process_is_spawned wait_for_socket ensure_class_is_defined end end - + def evaluate(code) ensure_context_is_defined call_js_method(EVALUATE_METHOD, code) end - + private - + def node_pid @@node_pid end - + def tmpdir @@tmpdir end def socket_path tmpdir && tmpdir.join(SOCKET_NAME) end - + def clsid self.class.clsid end - + def context_defined? @context_defined end - + def log_exception(e) return unless logger = Nodo.logger message = "\n#{e.class} (#{e.message})" message << ":\n\n#{e.backtrace.join("\n")}" if e.backtrace logger.error message end - + def ensure_process_is_spawned return if node_pid spawn_process end - + def ensure_class_is_defined return if self.class.class_defined? call_js_method(DEFINE_METHOD, self.class.generate_class_code) self.class.class_defined = true end - + def ensure_context_is_defined return if context_defined? @@mutex.synchronize do call_js_method(EVALUATE_METHOD, '') ObjectSpace.define_finalizer(self, self.class.send(:finalize_context, self.object_id)) @context_defined = true end end - + def spawn_process @@tmpdir = Pathname.new(Dir.mktmpdir('nodo')) env = Nodo.env.merge('NODE_PATH' => Nodo.modules_root.to_s) env['NODO_DEBUG'] = '1' if Nodo.debug @@node_pid = Process.spawn(env, Nodo.binary, '-e', self.class.generate_core_code, '--', socket_path.to_s, err: :out) @@ -213,11 +213,11 @@ Process.kill(:SIGTERM, node_pid) rescue Errno::ECHILD Process.wait(node_pid) rescue Errno::ECHILD FileUtils.remove_entry(tmpdir) if File.directory?(tmpdir) end end - + def wait_for_socket start = Time.now socket = nil while Time.now - start < LAUNCH_TIMEOUT begin @@ -255,11 +255,11 @@ raise TimeoutError, "function call #{self.class}##{method} timed out" rescue Errno::EPIPE, IOError # TODO: restart or something? If this happens the process is completely broken raise Error, 'Node process failed' end - + def handle_error(response, function) if response.body result = parse_response(response) error = if result.is_a?(Hash) && result['error'].is_a?(Hash) attrs = result['error'] @@ -268,22 +268,22 @@ end error ||= CallError.new("Node returned #{response.code}") log_exception(error) raise error end - + def parse_response(response) data = response.body.force_encoding('UTF-8') JSON.parse(data) unless data == '' end - + def with_tempfile(name) ext = File.extname(name) result = nil Tempfile.create([File.basename(name, ext), ext], tmpdir) do |file| result = yield(file) end result end - + end end