diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-28 18:07:04 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-02-28 18:07:04 +0200 |
commit | b97462993b00ecc5cd04051b4fc2abd164fefb04 (patch) | |
tree | 6575f4c47b658a69c7da60e72d1f748e866e0aaf /src/ipc.c | |
parent | 2583e020e7f1f1036f6df8f5b93668e7a0187d60 (diff) |
Single app instance; IPC mechanism
Only one instance of Lagrange is allowed to run per a runtime directory. Otherwise the instances will overwrite each others' files.
Added a check at startup to see if an instance is already running. If so, options can be used to communicate with it.
By default, a new tab is opened in the running instance.
IssueID #37
IssueID #164
IssueID #174
IssueID #178
Diffstat (limited to 'src/ipc.c')
-rw-r--r-- | src/ipc.c | 186 |
1 files changed, 186 insertions, 0 deletions
@@ -21,4 +21,190 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "ipc.h" | 23 | #include "ipc.h" |
24 | #include "app.h" | ||
24 | 25 | ||
26 | #include <the_Foundation/file.h> | ||
27 | #include <the_Foundation/fileinfo.h> | ||
28 | #include <the_Foundation/mutex.h> | ||
29 | #include <the_Foundation/path.h> | ||
30 | #include <the_Foundation/process.h> | ||
31 | #include <the_Foundation/time.h> | ||
32 | |||
33 | #include <signal.h> | ||
34 | |||
35 | iDeclareType(Ipc) | ||
36 | |||
37 | struct Impl_Ipc { | ||
38 | iString dir; | ||
39 | iBool isListening; | ||
40 | }; | ||
41 | |||
42 | static iIpc ipc_; | ||
43 | |||
44 | static const char *lockFilePath_(const iIpc *d) { | ||
45 | return concatPath_CStr(cstr_String(&d->dir), ".pid"); | ||
46 | } | ||
47 | |||
48 | static const char *inputFilePath_(const iIpc *d, int pid) { | ||
49 | return concatPath_CStr(cstr_String(&d->dir), | ||
50 | format_CStr(".run.%d.cfg", pid ? pid : currentId_Process())); | ||
51 | } | ||
52 | |||
53 | void init_Ipc(const char *runDir) { | ||
54 | iIpc *d = &ipc_; | ||
55 | initCStr_String(&d->dir, runDir); | ||
56 | d->isListening = iFalse; | ||
57 | signal(SIGUSR1, SIG_IGN); | ||
58 | } | ||
59 | |||
60 | void deinit_Ipc(void) { | ||
61 | iIpc *d = &ipc_; | ||
62 | signal(SIGUSR1, SIG_IGN); | ||
63 | if (d->isListening) { | ||
64 | remove(lockFilePath_(d)); | ||
65 | } | ||
66 | deinit_String(&d->dir); | ||
67 | } | ||
68 | |||
69 | static void handleUserSignal_(int sig) { | ||
70 | iIpc *d = &ipc_; | ||
71 | iAssert(sig == SIGUSR1); | ||
72 | iUnused(sig); | ||
73 | const char *path = inputFilePath_(d, 0); | ||
74 | iFile *f = newCStr_File(path); | ||
75 | if (open_File(f, readOnly_FileMode)) { | ||
76 | iString *cmds = new_String(); | ||
77 | initBlock_String(cmds, collect_Block(readAll_File(f))); | ||
78 | iRangecc line = iNullRange; | ||
79 | while (nextSplit_Rangecc(range_String(cmds), "\n", &line)) { | ||
80 | postCommand_App(cstr_Rangecc(line)); | ||
81 | } | ||
82 | delete_String(cmds); | ||
83 | } | ||
84 | iRelease(f); | ||
85 | remove(path); | ||
86 | } | ||
87 | |||
88 | void listen_Ipc(void) { | ||
89 | iIpc *d = &ipc_; | ||
90 | signal(SIGUSR1, handleUserSignal_); | ||
91 | iFile *f = newCStr_File(lockFilePath_(d)); | ||
92 | if (open_File(f, writeOnly_FileMode)) { | ||
93 | printf_Stream(stream_File(f), "%u", currentId_Process()); | ||
94 | d->isListening = iTrue; | ||
95 | } | ||
96 | iRelease(f); | ||
97 | } | ||
98 | |||
99 | iProcessId check_Ipc(void) { | ||
100 | const iIpc *d = &ipc_; | ||
101 | iProcessId pid = 0; | ||
102 | iFile *f = newCStr_File(lockFilePath_(d)); | ||
103 | if (open_File(f, readOnly_FileMode)) { | ||
104 | const iBlock *running = collect_Block(readAll_File(f)); | ||
105 | close_File(f); | ||
106 | pid = atoi(constData_Block(running)); | ||
107 | if (!exists_Process(pid)) { | ||
108 | pid = 0; | ||
109 | remove(cstr_String(path_File(f))); /* Stale. */ | ||
110 | } | ||
111 | } | ||
112 | iRelease(f); | ||
113 | return pid; | ||
114 | } | ||
115 | |||
116 | /*----------------------------------------------------------------------------------------------*/ | ||
117 | |||
118 | iDeclareType(IpcResponse) | ||
119 | |||
120 | struct Impl_IpcResponse { | ||
121 | iString *output; | ||
122 | iBool success; | ||
123 | iMutex mtx; | ||
124 | iCondition finished; | ||
125 | }; | ||
126 | |||
127 | static void init_IpcResponse(iIpcResponse *d) { | ||
128 | d->output = new_String(); | ||
129 | d->success = iFalse; | ||
130 | init_Mutex(&d->mtx); | ||
131 | init_Condition(&d->finished); | ||
132 | } | ||
133 | |||
134 | static void deinit_IpcResponse(iIpcResponse *d) { | ||
135 | deinit_Condition(&d->finished); | ||
136 | deinit_Mutex(&d->mtx); | ||
137 | delete_String(d->output); | ||
138 | } | ||
139 | |||
140 | iDefineTypeConstruction(IpcResponse) | ||
141 | |||
142 | static iIpcResponse *response_; | ||
143 | |||
144 | static void handleSignal_IpcResponse_(int sig) { | ||
145 | iUnused(sig); | ||
146 | iAssert(response_); | ||
147 | iIpcResponse *d = response_; | ||
148 | lock_Mutex(&d->mtx); | ||
149 | iFile *f = newCStr_File(inputFilePath_(&ipc_, 0)); | ||
150 | if (open_File(f, text_FileMode | readOnly_FileMode)) { | ||
151 | iBlock *input = readAll_File(f); | ||
152 | close_File(f); | ||
153 | remove(cstr_String(path_File(f))); | ||
154 | setBlock_String(d->output, input); | ||
155 | d->success = iTrue; | ||
156 | delete_Block(input); | ||
157 | } | ||
158 | iRelease(f); | ||
159 | signal_Condition(&d->finished); | ||
160 | unlock_Mutex(&d->mtx); | ||
161 | } | ||
162 | |||
163 | iBool write_Ipc(iProcessId pid, const iString *input, enum iIpcWrite type) { | ||
164 | iBool ok = iFalse; | ||
165 | iFile *f = newCStr_File(inputFilePath_(&ipc_, pid)); | ||
166 | if (open_File(f, text_FileMode | append_FileMode)) { | ||
167 | write_File(f, utf8_String(input)); | ||
168 | if (type == command_IpcWrite) { | ||
169 | printf_Stream(stream_File(f), "\nipc.signal arg:%d\n", currentId_Process()); | ||
170 | } | ||
171 | close_File(f); | ||
172 | ok = iTrue; | ||
173 | } | ||
174 | iRelease(f); | ||
175 | return ok; | ||
176 | } | ||
177 | |||
178 | iString *communicate_Ipc(const iString *command) { | ||
179 | const iProcessId dst = check_Ipc(); | ||
180 | if (dst) { | ||
181 | if (write_Ipc(dst, command, command_IpcWrite)) { | ||
182 | response_ = new_IpcResponse(); | ||
183 | signal(SIGUSR1, handleSignal_IpcResponse_); | ||
184 | lock_Mutex(&response_->mtx); | ||
185 | if (kill(dst, SIGUSR1) == 0) { | ||
186 | iTime until; | ||
187 | initTimeout_Time(&until, 1.0); | ||
188 | waitTimeout_Condition(&response_->finished, &response_->mtx, &until); | ||
189 | } | ||
190 | unlock_Mutex(&response_->mtx); | ||
191 | if (!response_->success) { | ||
192 | delete_IpcResponse(response_); | ||
193 | response_ = NULL; | ||
194 | } | ||
195 | } | ||
196 | } | ||
197 | signal(SIGUSR1, SIG_IGN); | ||
198 | if (response_) { | ||
199 | iString *result = copy_String(response_->output); | ||
200 | trimEnd_String(result); | ||
201 | delete_IpcResponse(response_); | ||
202 | response_ = NULL; | ||
203 | return result; | ||
204 | } | ||
205 | return NULL; | ||
206 | } | ||
207 | |||
208 | void signal_Ipc(iProcessId pid) { | ||
209 | kill(pid, SIGUSR1); | ||
210 | } | ||