Since we can not guarantee that a user-supplied formatter is side-effect
free, a delayed formatting might result in unexpected results. An
example of such a formatter is the one used by
ActiveSupport::TaggedLogging.
This helps in ensuring thread-safety when flushing unbuffered Buffers.
Previously, it was possibly for client code to still hold references to
fields on a flushed and cleared buffer, potentially resulting in
unintended side-effects if the client code was not aware that the buffer
was cleared.
By creating ompletely new object instances, existing references only
point to data from before the clear. This ensures a clean cut.
This is functionally equivalent. However, the previous behavior resulted
in the block being materialized as a proc which is quite expensive, both
during materialization as well as when calling it. By using
`block_given?`, we can avoid this materialization.
These methods are useful convenience methods to add (nested) fields to a
hash while optionally retaining existing values. Usually, `#deep_merge!`
or `#set` will be the commonly used ways to set fields to a hash.
This follows the rule that two objects which are eql? also have the same
hash value. It is also required to ensure that we can use collections in
sets, hashes and arrays where the hash-equality is checked for certain
operations.
This allows to define default values for certain fields which can be
inserted just before a Buffer is flushed. They won;t overwrite prior
user-provided fields.
Through the use of a block, expensive calculations for a field could be
avoided if the field is not going to be inserted, e.g. because it exists
already or is forbidden from being set.
That way, we can ensure that the BufferStack and the Buffers themselves
including their nested fields can not be accessed by different threads,
providing some thread safety for non malicious users.
Using a block here is unnecessary and doesn't help us with any
thread-safty guarantees on deeply nested fields or tags. Thus, we can
just remove this and replace it with a simpler method returning the
top-most buffer.
When using this buffer, we still have to ensure that only a single
Thread can access it.
Generally, we try hard to avoid additional dependencies to external gems
to keep us from having to maintain all these dependencies in the variety
of environments where Rackstash is going to be used.
We still decided to depend on two concurrent-ruby gems since they are
1. are of exceptional code quality
2. are well-maintained and with devs eager to maintain a stable and
well-understood interface
3. provide very useful buolding-blocks for safe interoperations across
thread-boundaries.
The chosen versions are selected to be compatible with a wide range of
external frameworks.