Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

lock_free/Sieve_Cache.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion feature lock_free.Sieve_Cache
#
# -----------------------------------------------------------------------


Node(K type, V type, pair (K,V), visited concur.atomic bool, prev concur.atomic (option (lock_free.Node K V)), next concur.atomic (option (lock_free.Node K V))) ref is
  redef as_string => "({pair.0},{pair.1})"


# "SIEVE is a cache eviction algorithm that decides what to keep in the cache and what to discard.
#  It achieves both simplicity and efficiency."
# source: https://sievecache.com/
# paper : https://junchengyang.com/publication/nsdi24-SIEVE.pdf
#
# example usage:
#     cache0 := lock_free.Sieve_Cache String i32 3
#     say <| cache0.access "A" ()->1    # => 1
#     say <| cache0.access "B" ()->2    # => 2
#     say <| cache0.access "C" ()->3    # => 3
#     say <| cache0.access "A" ()->4    # => 1
#     say <| cache0.access "D" ()->5    # => 5
#     say <| cache0.access "C" ()->6    # => 3
#
public Sieve_Cache(K type : property.hashable, V type, public capacity i32) ref is

  # NYI: map implementation agnostic
  cache := (lock_free.Map K (lock_free.Node K V)).type.empty

  # Using atomic here to be able to read and write in a
  # multi threaded environment.
  head  := concur.atomic (option (lock_free.Node K V)) nil
  tail  := concur.atomic (option (lock_free.Node K V)) nil
  hand  := concur.atomic (option (lock_free.Node K V)) nil

  add_to_head(n lock_free.Node K V) =>
    n.next.write head.read
    n.prev.write nil
    match head.read
      h lock_free.Node => h.prev.write n
      * =>
    head.write n
    match tail.read
      nil => tail.write n
      * =>

  remove_node(n lock_free.Node K V) =>
    match n.prev.read
      prev lock_free.Node => prev.next.write n.next
      nil => head.write n.next

    match n.next.read
      nxt lock_free.Node => nxt.prev.write n.prev
      nil => tail.write n.prev

  evict =>
    init_obj => match hand.read
                  h lock_free.Node => h
                  nil => tail.read
    for obj := init_obj, match obj.val.prev.read
                           p lock_free.Node => p
                           nil => tail.read
    while match obj
            nil => false
            o lock_free.Node => o.visited.read
    do
      obj.val.visited.write false
    else
      match obj
        o lock_free.Node =>
          hand.write o.prev
          _ := cache.remove o.pair.0
          remove_node o
        nil => hand.write nil


  # access stored value for key,
  # if not in cache, compute and store in cache
  #
  public access(key K, val ()->V) =>
    match cache[key]
      # cache miss
      nil =>
        v := val()
        if cache.size >= capacity
          evict
        new_node := lock_free.Node (key, v) (concur.atomic false) (concur.atomic (option (lock_free.Node K V)) nil) (concur.atomic (option (lock_free.Node K V)) nil)
        add_to_head new_node
        cache.put key new_node
        new_node.visited.write false
        v
      # cache hit
      n lock_free.Node =>
        n.visited.write true
        n.pair.1


  public redef as_string =>
    for res := "", res + "{n.val} (Visited: {n.val.visited.read})" + (if n.val.next.read?? then " -> " else "")
        n := head.read, n.val.next
    while n??
    else
      res
last changed: 2025-01-30