From 8a170e4afc91977a5468e2ded67a33fa9de2bf90 Mon Sep 17 00:00:00 2001
From: Chris Sinjakli <chris@gocardless.com>
Date: Fri, 26 Jul 2019 13:33:43 +0100
Subject: [PATCH] Reopen files in DirectFileStore when the process has forked

Currently, if you fork after altering a metric value when using
DirectFileStore, the child process will have a handle to the original
process's metric store.

This commit detects checks for that condition on any instrumentation
event and opens a new metric file for the new PID.

Signed-off-by: Chris Sinjakli <chris@gocardless.com>
---
 .../client/data_stores/direct_file_store.rb          |  7 ++++++-
 .../client/data_stores/direct_file_store_spec.rb     | 12 ++++++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/lib/prometheus/client/data_stores/direct_file_store.rb b/lib/prometheus/client/data_stores/direct_file_store.rb
index e9b0dcd3..9dbab76e 100644
--- a/lib/prometheus/client/data_stores/direct_file_store.rb
+++ b/lib/prometheus/client/data_stores/direct_file_store.rb
@@ -154,7 +154,12 @@ def store_key(labels)
           end
 
           def internal_store
-            @internal_store ||= FileMappedDict.new(filemap_filename)
+            if @store_opened_by_pid != process_id
+              @store_opened_by_pid = process_id
+              @internal_store = FileMappedDict.new(filemap_filename)
+            else
+              @internal_store
+            end
           end
 
           # Filename for this metric's PStore (one per process)
diff --git a/spec/prometheus/client/data_stores/direct_file_store_spec.rb b/spec/prometheus/client/data_stores/direct_file_store_spec.rb
index 52611adb..ba9d7d15 100644
--- a/spec/prometheus/client/data_stores/direct_file_store_spec.rb
+++ b/spec/prometheus/client/data_stores/direct_file_store_spec.rb
@@ -57,6 +57,18 @@
     ms2.increment(labels: {}, by: 1)
   end
 
+  context "when process is forked" do
+    it "opens a new internal store to avoid two processes using the same file" do
+      allow(Process).to receive(:pid).and_return(12345)
+      metric_store = subject.for_metric(:metric_name, metric_type: :counter)
+      metric_store.increment(labels: {}, by: 1)
+
+      allow(Process).to receive(:pid).and_return(23456)
+      metric_store.increment(labels: {}, by: 1)
+      expect(Dir.glob('/tmp/prometheus_test/*').size).to eq(2)
+      expect(metric_store.all_values).to eq({} => 2.0)
+    end
+  end
 
   context "for a non-gauge metric" do
     it "sums values from different processes by default" do