Generic SmartCore functionality.
Installation
gem 'smart_engine'
bundle install
# --- or ---
gem install smart_engine
require 'smart_core'
Technologies
- Global set of error types
- Simple reentrant lock
- Read/Write Lock
- Cache Storage
- Atomic thread-safe value container
- Any Object Frozener (classic c-level
frozen?
/freeze
) - Basic Object Refinements (
SmartCore::Ext::BasicObjectAsObject
) - Inline rescue pipe
Global set of error types
-
SmartCore::Error
(inherited from::StandardError
); -
SmartCore::ArgumentError
(inherited from::ArgumentError
); -
SmartCore::FrozenError
(inherited from::FrozenError
); -
SmartCore::NameError
(inherited from::NameError
); -
SmartCore::TypeError
(inherited from::TypeError
);
Simple reentrant lock
lock = SmartCore::Engine::Lock.new
lock.synchronize { your_code }
Read/Write Lock
- non-controlable reader count;
- readers does not lock each other;
- readers waits for writer;
- writer waits for readers;
lock = SmartCore::Engine::ReadWriteLock.new
lock.read_sync { ...some-read-op... } # waits for writer
lock.read_sync { ...some-read-op... } # waits for writer
lock.write_sync { ... some-write-op... } # waits for all readers and current writer
# is write_sync lock is owned by current thread?
lock.write_owned? # true or false
Cache Storage
-
you can use any object as a cache key;
-
you can store any object as a cache value;
-
you can cache
nil
object too; -
cache
read
hasfetch
semantics:- signature:
#read(key, &fallback)
; - in the event of cache miss the
&fallback
black will be invoked; - the return value of the fallback block will be written to the cache, and that return value will be returned;
- signature:
-
cache
write
:- signature:
#write(key, value)
; - you can use any object as a cache key;
- you can store any object as a value;
- you can write
nil
object too;
- signature:
-
cache clear:
- signature:
#clear
;
- signature:
cache = SmartCore::Engine::Cache.new
# write and read
cache.write(:amount, 123.456) # => 123.456
cache.read(:amount) # => 123.456
# read non-existing with a fallback
cache.read('name') # => nil
cache.read('name') { 'D@iVeR' } # => 'D@iVeR'
cache.read('name') # => 'D@iVeR'
# store nil object
cache.write(:nil_value, nil) # => nil
cache.read(:nil_value) # => nil
cache.read(:nil_value) { 'rewritten' } # => nil
cache.read(:nil_value) # => nil
# clear cache
cache.clear # => nil
# aliases:
# write:
cache[:key1] = 'test'
# read:
cache[:key1] # => 'test'
# read with fallback:
cache[:key2] { 'test2' } # => 'test2'
cache[:key2] # => 'test2'
Atomic thread-safe value container
atom = SmartCore::Engine::Atom.new # initial value - nil
atom.value # => nil
# --- or ---
atom = SmartCore::Engine::Atom.new(7) # initial value - 7
atom.value # => 7
# set new value (thread-safely)
atom.swap { |original_value| original_value * 2 }
atom.value # => 14
Any Object Frozener
- works with any type of ruby objects (event with
BasicObject
); - uses classic Ruby C-level
frozen?
/freeze
functionality;
# as a singleton
object = BasicObject.new
SmartCore::Engine::Frozener.frozen?(object) # => false
SmartCore::Engine::Frozener.freeze(object)
SmartCore::Engine::Frozener.frozen?(object) # => true
# as a mixin
class EmptyObject < BasicObject
include SmartCore::Engine::Frozener::Mixin
end
object = EmptyObject.new
object.frozen? # => false
object.freeze
object.frozen? # => true
Basic Object Refinements
Ruby's BasicObject
class does not have some fundamental (extremely important for instrumenting) methods:
-
is_a?
/kind_of?
instance_of?
-
freeze
/frozen?
hash
nil?
inspect
SmartCore::Ext::BasicObjectAsObject
refinement solves this problem (by Ruby's internal API without any manualy-emulated behavior).
# without refinement:
basic_obj = ::BasicObject.new
basic_obj.is_a?(::BasicObject) # raises ::NoMethodError
basic_obj.kind_of?(::BasicObject) # raises ::NoMethodError
basic_obj.instance_of?(::BasicObject) # rasies ::NoMethodError
basic_obj.freeze # raises ::NoMethodError
basic_obj.frozen? # raises ::NoMethodError
basic_object.hash # raises ::NoMethodError
basic_object.nil? # raises ::NoMethodError
basic_object.inspect # raises ::NoMethodError
# with refinement:
using SmartCore::Ext::BasicObjectAsObject
basic_obj = ::BasicObject.new
basic_obj.is_a?(::BasicObject) # => true
basic_obj.kind_of?(::BasicObject) # => true
basic_obj.instance_of?(::BasicObject) # => true
basic_obj.instance_of?(::Object) # => false
basic_obj.is_a?(::Integer) # => false
basic_obj.kind_of?(::Integer) # => false
basic_obj.frozen? # => false
basic_obj.freeze # => self
basic_obj.frozen? # => true
basic_obj.nil? # => false
basic_obj.hash # => 2682859680348634421 (some Integer value)
basic_obj.inspect # => "#<BasicObject:0x00007fe428018628>"
Inline rescue pipe
- works with an array of proc objects;
- returns the result of the first non-failed proc;
- provides an error interception interface (a block argument);
- fails with the last failed proc exception (if all procs were failed and interceptor was not passed);
Return the result of the first non-failed proc
SmartCore::Engine::RescueExt.inline_rescue_pipe(
-> { raise },
-> { raise },
-> { 123 },
-> { 567 },
-> { raise },
)
# => output: 123
Fail with the last failed proc exception
SmartCore::Engine::RescueExt.inline_rescue_pipe(
-> { raise(::ArgumentError) },
-> { raise(::TypeError) },
-> { raise(::ZeroDivisionError) }
)
# => fails with ZeroDivisionError
Error interception
SmartCore::Engine::RescueExt.inline_rescue_pipe(
-> { raise(::ArgumentError) },
-> { raise(::TypeError) },
-> { raise(::ZeroDivisionError, 'Intercepted exception') }
) do |error|
error.message
end
# => output: "Intercepted exception"
Roadmap
- migrate to Github Actions in CI;
- thread-safety for BasicObject extensions;
-
SmartCore::Engine::Cache
:- thread-safety;
- support for
ttl:
option for#write
and for fallback block attribute of#read
; - support for key-value-pair iteration;
- support for
#keys
method; - support for
#key?
method; - think about some layer of cache object serialization;
-
SmartCore::Engine::ReadWriteLock
:- an ability to set a maximum count of readers;
Contributing
- Fork it ( https://github.com/smart-rb/smart_engine )
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am '[feature_context] Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create new Pull Request
License
Released under MIT License.