@@ -2,31 +2,40 @@ defmodule Realtime.Tenants.Connect do
2
2
@ moduledoc """
3
3
This module is responsible for attempting to connect to a tenant's database and store the DBConnection in a Syn registry.
4
4
"""
5
- use GenServer
5
+ use GenServer , restart: :transient
6
6
7
7
require Logger
8
8
9
9
alias Realtime.Helpers
10
10
alias Realtime.Tenants
11
+ alias Realtime.UsersCounter
11
12
12
- defstruct tenant_id: nil , db_conn_reference: nil
13
+ @ erpc_timeout_default 5000
14
+ @ check_connected_user_interval_default 1000
15
+ @ connected_users_bucket_shutdown [ 0 , 0 , 0 , 0 , 0 , 0 ]
16
+
17
+ defstruct tenant_id: nil ,
18
+ db_conn_reference: nil ,
19
+ db_conn_pid: nil ,
20
+ check_connected_user_interval: nil ,
21
+ connected_users_bucket: [ 1 ]
13
22
14
23
@ doc """
15
24
Returns the database connection for a tenant. If the tenant is not connected, it will attempt to connect to the tenant's database.
16
25
"""
17
- @ spec lookup_or_start_connection ( binary ( ) ) :: { :ok , DBConnection . t ( ) } | { :error , term ( ) }
18
- def lookup_or_start_connection ( tenant_id ) do
26
+ @ spec lookup_or_start_connection ( binary ( ) , keyword ( ) ) ::
27
+ { :ok , DBConnection . t ( ) } | { :error , term ( ) }
28
+ def lookup_or_start_connection ( tenant_id , opts \\ [ ] ) do
19
29
case get_status ( tenant_id ) do
20
30
{ :ok , conn } -> { :ok , conn }
21
- { :error , :tenant_database_unavailable } -> call_external_node ( tenant_id )
31
+ { :error , :tenant_database_unavailable } -> call_external_node ( tenant_id , opts )
22
32
{ :error , :initializing } -> { :error , :tenant_database_unavailable }
23
33
end
24
34
end
25
35
26
36
@ doc """
27
37
Returns the database connection pid from :syn if it exists.
28
38
"""
29
-
30
39
@ spec get_status ( binary ( ) ) ::
31
40
{ :ok , DBConnection . t ( ) } | { :error , :tenant_database_unavailable | :initializing }
32
41
def get_status ( tenant_id ) do
@@ -40,10 +49,10 @@ defmodule Realtime.Tenants.Connect do
40
49
@ doc """
41
50
Connects to a tenant's database and stores the DBConnection in the process :syn metadata
42
51
"""
43
- @ spec connect ( binary ( ) ) :: { :ok , DBConnection . t ( ) } | { :error , term ( ) }
44
- def connect ( tenant_id ) do
52
+ @ spec connect ( binary ( ) , keyword ( ) ) :: { :ok , DBConnection . t ( ) } | { :error , term ( ) }
53
+ def connect ( tenant_id , opts \\ [ ] ) do
45
54
supervisor = { :via , PartitionSupervisor , { Realtime.Tenants.Connect.DynamicSupervisor , self ( ) } }
46
- spec = { __MODULE__ , tenant_id: tenant_id }
55
+ spec = { __MODULE__ , [ tenant_id: tenant_id ] ++ opts }
47
56
48
57
case DynamicSupervisor . start_child ( supervisor , spec ) do
49
58
{ :ok , _ } -> get_status ( tenant_id )
@@ -52,9 +61,20 @@ defmodule Realtime.Tenants.Connect do
52
61
end
53
62
end
54
63
55
- def start_link ( tenant_id: tenant_id ) do
64
+ def start_link ( opts ) do
65
+ tenant_id = Keyword . get ( opts , :tenant_id )
66
+
67
+ check_connected_user_interval =
68
+ Keyword . get ( opts , :check_connected_user_interval , @ check_connected_user_interval_default )
69
+
56
70
name = { __MODULE__ , tenant_id , % { conn: nil } }
57
- GenServer . start_link ( __MODULE__ , % __MODULE__ { tenant_id: tenant_id } , name: { :via , :syn , name } )
71
+
72
+ state = % __MODULE__ {
73
+ tenant_id: tenant_id ,
74
+ check_connected_user_interval: check_connected_user_interval
75
+ }
76
+
77
+ GenServer . start_link ( __MODULE__ , state , name: { :via , :syn , name } )
58
78
end
59
79
60
80
## GenServer callbacks
@@ -66,9 +86,9 @@ defmodule Realtime.Tenants.Connect do
66
86
case res do
67
87
{ :ok , conn } ->
68
88
:syn . update_registry ( __MODULE__ , tenant_id , fn _pid , meta -> % { meta | conn: conn } end )
69
- state = % { state | db_conn_reference: Process . monitor ( conn ) }
89
+ state = % { state | db_conn_reference: Process . monitor ( conn ) , db_conn_pid: conn }
70
90
71
- { :ok , state }
91
+ { :ok , state , { :continue , :setup_connected_users } }
72
92
73
93
{ :error , error } ->
74
94
Logger . error ( "Error connecting to tenant database: #{ inspect ( error ) } " )
@@ -77,6 +97,41 @@ defmodule Realtime.Tenants.Connect do
77
97
end
78
98
end
79
99
100
+ @ impl GenServer
101
+ def handle_continue (
102
+ :setup_connected_users ,
103
+ % {
104
+ check_connected_user_interval: check_connected_user_interval ,
105
+ connected_users_bucket: connected_users_bucket
106
+ } = state
107
+ ) do
108
+ send_connected_user_check_message ( connected_users_bucket , check_connected_user_interval )
109
+ { :noreply , state }
110
+ end
111
+
112
+ @ impl GenServer
113
+ def handle_info (
114
+ :check_connected_users ,
115
+ % {
116
+ tenant_id: tenant_id ,
117
+ check_connected_user_interval: check_connected_user_interval ,
118
+ connected_users_bucket: connected_users_bucket
119
+ } = state
120
+ ) do
121
+ connected_users_bucket =
122
+ tenant_id
123
+ |> update_connected_users_bucket ( connected_users_bucket )
124
+ |> send_connected_user_check_message ( check_connected_user_interval )
125
+
126
+ { :noreply , % { state | connected_users_bucket: connected_users_bucket } }
127
+ end
128
+
129
+ def handle_info ( :shutdown , % { db_conn_pid: db_conn_pid } = state ) do
130
+ Logger . info ( "Tenant has no connected users, database connection will be terminated" )
131
+ :ok = GenServer . stop ( db_conn_pid )
132
+ { :stop , :normal , state }
133
+ end
134
+
80
135
@ impl GenServer
81
136
def handle_info (
82
137
{ :DOWN , db_conn_reference , _ , _ , _ } ,
@@ -88,10 +143,28 @@ defmodule Realtime.Tenants.Connect do
88
143
89
144
## Private functions
90
145
91
- defp call_external_node ( tenant_id ) do
146
+ defp call_external_node ( tenant_id , opts ) do
92
147
with tenant <- Tenants.Cache . get_tenant_by_external_id ( tenant_id ) ,
93
148
{ :ok , node } <- Realtime.Nodes . get_node_for_tenant ( tenant ) do
94
- :erpc . call ( node , __MODULE__ , :connect , [ tenant_id ] , 5000 )
149
+ :erpc . call ( node , __MODULE__ , :connect , [ tenant_id , opts ] , @ erpc_timeout_default )
95
150
end
96
151
end
152
+
153
+ defp update_connected_users_bucket ( tenant_id , connected_users_bucket ) do
154
+ connected_users_bucket
155
+ |> then ( & ( & 1 ++ [ UsersCounter . tenant_users ( tenant_id ) ] ) )
156
+ |> Enum . take ( - 6 )
157
+ end
158
+
159
+ defp send_connected_user_check_message (
160
+ @ connected_users_bucket_shutdown ,
161
+ check_connected_user_interval
162
+ ) do
163
+ Process . send_after ( self ( ) , :shutdown , check_connected_user_interval )
164
+ end
165
+
166
+ defp send_connected_user_check_message ( connected_users_bucket , check_connected_user_interval ) do
167
+ Process . send_after ( self ( ) , :check_connected_users , check_connected_user_interval )
168
+ connected_users_bucket
169
+ end
97
170
end
0 commit comments