11defmodule Sentry.Sources do
22 @ moduledoc false
33
4+ use GenServer
5+
46 alias Sentry.Config
57
8+ @ type source_map_for_file :: % {
9+ optional ( line_no :: pos_integer ( ) ) => line_contents :: String . t ( )
10+ }
11+
612 @ type source_map :: % {
7- optional ( String . t ( ) ) => % {
8- ( line_no :: pos_integer ( ) ) => line_contents :: String . t ( )
9- }
13+ optional ( String . t ( ) ) => source_map_for_file ( )
1014 }
1115
12- @ source_code_map_key { :sentry , :source_code_map }
16+ ## GenServer
17+
18+ @ table __MODULE__
19+
20+ @ spec start_link ( keyword ( ) ) :: GenServer . on_start ( )
21+ def start_link ( [ ] = _ ) do
22+ GenServer . start_link ( __MODULE__ , nil , name: __MODULE__ )
23+ end
24+
25+ @ impl true
26+ def init ( nil ) do
27+ _ = :ets . new ( @ table , [ :public , :named_table , read_concurrency: true ] )
28+ { :ok , :no_state , { :continue , :load_source_code_map } }
29+ end
30+
31+ @ impl true
32+ def handle_continue ( :load_source_code_map , state ) do
33+ :ok =
34+ with { :loaded , source_map } <- load_source_code_map_if_present ( ) do
35+ Enum . each ( source_map , fn { path , lines_map } ->
36+ :ets . insert ( @ table , { path , lines_map } )
37+ end )
38+ else
39+ _error -> :ok
40+ end
41+
42+ { :noreply , state }
43+ end
44+
45+ ## Other functions
46+
1347 @ compression_level if Mix . env ( ) == :test , do: 0 , else: 9
1448
1549 # Default argument is here for testing.
@@ -21,7 +55,6 @@ defmodule Sentry.Sources do
2155
2256 with { :ok , contents } <- File . read ( path ) ,
2357 { :ok , source_map } <- decode_source_code_map ( contents ) do
24- :persistent_term . put ( @ source_code_map_key , source_map )
2558 { :loaded , source_map }
2659 else
2760 { :error , :binary_to_term } ->
@@ -67,11 +100,6 @@ defmodule Sentry.Sources do
67100 end
68101 end
69102
70- @ spec get_source_code_map_from_persistent_term ( ) :: source_map ( ) | nil
71- def get_source_code_map_from_persistent_term do
72- :persistent_term . get ( @ source_code_map_key , nil )
73- end
74-
75103 @ spec load_files ( keyword ( ) ) :: { :ok , source_map ( ) } | { :error , message :: String . t ( ) }
76104 def load_files ( config \\ [ ] ) when is_list ( config ) do
77105 config = Sentry.Config . validate! ( config )
@@ -106,23 +134,25 @@ defmodule Sentry.Sources do
106134 source_map -> { :ok , source_map }
107135 end
108136
109- @ spec get_source_context ( source_map ( ) , String . t ( ) | nil , pos_integer ( ) | nil ) ::
110- { [ String . t ( ) ] , String . t ( ) | nil , [ String . t ( ) ] }
111- def get_source_context ( % { } = files , file_name , line_number ) do
112- context_lines = Config . context_lines ( )
113-
114- case Map . fetch ( files , file_name ) do
115- :error -> { [ ] , nil , [ ] }
116- { :ok , file } -> get_source_context_for_file ( file , line_number , context_lines )
137+ @ spec get_lines_for_file ( Path . t ( ) ) :: map ( ) | nil
138+ def get_lines_for_file ( file ) do
139+ case :ets . lookup ( @ table , file ) do
140+ [ { ^ file , lines } ] -> lines
141+ [ ] -> nil
117142 end
118143 end
119144
120- defp get_source_context_for_file ( file , line_number , context_lines ) do
145+ @ spec get_source_context ( source_map_for_file ( ) , pos_integer ( ) | nil ) ::
146+ { [ String . t ( ) ] , String . t ( ) | nil , [ String . t ( ) ] }
147+ def get_source_context ( source_map_for_file , line_number )
148+ when is_map ( source_map_for_file ) and ( is_integer ( line_number ) or is_nil ( line_number ) ) do
149+ context_lines = Config . context_lines ( )
150+
121151 context_line_indices = 0 .. ( 2 * context_lines )
122152
123153 Enum . reduce ( context_line_indices , { [ ] , nil , [ ] } , fn i , { pre_context , context , post_context } ->
124154 context_line_number = line_number - context_lines + i
125- source = Map . get ( file , context_line_number )
155+ source = Map . get ( source_map_for_file , context_line_number )
126156
127157 cond do
128158 context_line_number == line_number && source ->
0 commit comments