ruby-concurrency/concurrent-ruby · error · TypeError
can't dump hash with default proc
Error message
can't dump hash with default proc
What it means
Concurrent::Map implements Marshal support via marshal_dump (map.rb:304-309). If the Map was created with a default proc (Concurrent::Map.new { |m, k| ... }), the proc cannot be serialized, so marshal_dump proactively raises TypeError ("can't dump hash with default proc"), matching Ruby's Hash behavior. The entries are not the problem; the unserializable proc is.
Source
Thrown at lib/concurrent-ruby/concurrent/map.rb:306
# Is map empty?
# @return [true, false]
def empty?
each_pair { |k, v| return false }
true
end unless method_defined?(:empty?)
# The size of map.
# @return [Integer] size
def size
count = 0
each_pair { |k, v| count += 1 }
count
end unless method_defined?(:size)
# @!visibility private
def marshal_dump
raise TypeError, "can't dump hash with default proc" if @default_proc
h = {}
each_pair { |k, v| h[k] = v }
h
end
# @!visibility private
def marshal_load(hash)
initialize
populate_from(hash)
end
undef :freeze
# @!visibility private
def inspect
format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect
end
View on GitHub (pinned to 0b88d5ff75)
Solutions
- Do not build the Map with a default proc: create it plain and read with map.fetch(k) { default }.
- Convert to a plain Hash before dumping: map.each_pair.with_object({}) { |(k, v), h| h[k] = v }.
- If the default proc is required, exclude the Map from Marshal payloads and persist only its entries.
Example fix
# before
stats = Concurrent::Map.new { |m, k| m[k] = 0 }
Marshal.dump(stats) # TypeError: can't dump hash with default proc
# after
stats = Concurrent::Map.new
stats.fetch(k) { stats[k] = 0 }
Marshal.dump(stats.each_pair.with_object({}) { |(k, v), h| h[k] = v }) Defensive patterns
Strategy: validation
Validate before calling
# Track whether the Map carries a default proc and dump entries, not the Map
has_default_proc = true/false # known at construction time
payload = has_default_proc ? map.each_pair.with_object({}) { |(k, v), h| h[k] = v } : map
Marshal.dump(payload) Try / catch
begin
Marshal.dump(map)
rescue TypeError
Marshal.dump(map.each_pair.with_object({}) { |(k, v), h| h[k] = v })
end Prevention
- Avoid default procs on Maps destined for serialization; use fetch with a block.
- Dump plain Hash snapshots, never live concurrency primitives.
- Keep Concurrent::Map instances out of job arguments and cached payloads.
When it happens
Trigger: Marshal.dump(Concurrent::Map.new { |m, k| 0 }); a Map created with a default_proc passed through Marshal.dump, DRb, or fork-based IPC; a caching layer (Rails cache, Sidekiq job arguments) serializing an object graph that contains a default-proc Map.
Common situations: Counter/memoization maps built with default-proc blocks and then shipped to a background job; deep state objects containing a Concurrent::Map dumped for persistence; DRb distribution of shared state.
Related errors
- key not found
- :initial_capacity must be a positive Integer
- :load_factor must be a number between 0 and 1
- NullPointerException
- IllegalStateException
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/a9998089b4f89bea.
Report an issue: GitHub.