File: lazy_method_map.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (98 lines) | stat: -rw-r--r-- 2,943 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# frozen_string_literal: true
require 'thread'
begin
  require 'concurrent'
rescue LoadError
  # no problem, we'll fallback to our own map
end

module GraphQL
  module Execution
    class Lazy
      # {GraphQL::Schema} uses this to match returned values to lazy resolution methods.
      # Methods may be registered for classes, they apply to its subclasses also.
      # The result of this lookup is cached for future resolutions.
      # Instances of this class are thread-safe.
      # @api private
      # @see {Schema#lazy?} looks up values from this map
      class LazyMethodMap
        def initialize(use_concurrent: defined?(Concurrent::Map))
          @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new
        end

        def initialize_copy(other)
          @storage = other.storage.dup
        end

        # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used)
        # @param lazy_value_method [Symbol] The method to call on this class to get its value
        def set(lazy_class, lazy_value_method)
          @storage[lazy_class] = lazy_value_method
        end

        # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses
        # @return [Symbol, nil] The `lazy_value_method` for this object, or nil
        def get(value)
          @storage.compute_if_absent(value.class) { find_superclass_method(value.class) }
        end

        def each
          @storage.each_pair { |k, v| yield(k, v) }
        end

        protected

        attr_reader :storage

        private

        def find_superclass_method(value_class)
          @storage.each_pair { |lazy_class, lazy_value_method|
            return lazy_value_method if value_class < lazy_class
          }
          nil
        end

        # Mock the Concurrent::Map API
        class ConcurrentishMap
          extend Forwardable
          # Technically this should be under the mutex too,
          # but I know it's only used when the lock is already acquired.
          def_delegators :@storage, :each_pair, :size

          def initialize
            @semaphore = Mutex.new
            # Access to this hash must always be managed by the mutex
            # since it may be modified at runtime
            @storage = {}
          end

          def []=(key, value)
            @semaphore.synchronize {
              @storage[key] = value
            }
          end

          def compute_if_absent(key)
            @semaphore.synchronize {
              @storage.fetch(key) { @storage[key] = yield }
            }
          end

          def initialize_copy(other)
            @semaphore = Mutex.new
            @storage = other.copy_storage
          end

          protected

          def copy_storage
            @semaphore.synchronize {
              @storage.dup
            }
          end
        end
      end
    end
  end
end