Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use thread locals to cache relationship instances #638

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/axlsx/rels/relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class << self
# Keeps track of all instances of this class.
# @return [Array]
def instances
@instances ||= []
Thread.current["#{name}.instances"] ||= []
end

# Clear cached instances.
Expand All @@ -21,7 +21,7 @@ def instances
# Also, calling this avoids memory leaks (cached instances lingering around
# forever).
def clear_cached_instances
@instances = []
Thread.current["#{name}.instances"] = []
end

# Generate and return a unique id (eg. `rId123`) Used for setting {#Id}.
Expand All @@ -30,7 +30,7 @@ def clear_cached_instances
# {clear_cached_instances} will automatically reset the generated ids, too.
# @return [String]
def next_free_id
"rId#{@instances.size + 1}"
"rId#{instances.size + 1}"
end
end

Expand Down
81 changes: 81 additions & 0 deletions test/rels/tc_relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,85 @@ def test_ampersand_escaping_in_target
doc = Nokogiri::XML(r.to_xml_string)
assert_equal(doc.xpath("//Relationship[@Target='http://example.com?foo=1&bar=2']").size, 1)
end

def test_instances_cache_single_thread
d1 = doc
d2 = doc

package(d1)
package(d2)

worksheet(d1)
worksheet(d2)

xlsx1 = serialize(d1)
xlsx2 = serialize(d2)

assert_equal(read_workbook(xlsx1), read_workbook(xlsx2))
end

def test_instances_cache_thread_safety
d1 = doc
d2 = doc

q1 = Queue.new
q2 = Queue.new

t1 = thread(d1, q1)
t2 = thread(d2, q2)

q1.push(:package)
q2.push(:package)

q1.push(:worksheet)
q2.push(:worksheet)

q1.push(:serialize)
q2.push(:serialize)

q1.push(:done)
q2.push(:done)

t1.join
t2.join

assert_equal(read_workbook(t1[:serialize]), read_workbook(t2[:serialize]))
end

private

def doc
{}
end

def package(doc)
doc[:package] = Axlsx::Package.new
end

def worksheet(doc)
doc[:package].workbook.add_worksheet(name: 'My Sheet')
end

def serialize(doc)
doc[:package].to_stream.string
end

def thread(doc, queue)
Thread.new do
while (message = queue.pop) != :done
Thread.current[message] = send(message, doc)
end
end
end

def read_workbook(xlsx)
Zip::InputStream.open(StringIO.new(xlsx)) do |io|
res = nil
while (entry = io.get_next_entry)
res = io.read if entry.name == 'xl/workbook.xml'
end
res
end
end

end