1 /*
2 * RSuspend.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include <shared_core/FilePath.hpp>
17
18 #include <Rembedded.h>
19
20 #include <r/RExec.hpp>
21 #include <r/session/RClientState.hpp>
22 #include <r/session/RGraphics.hpp>
23 #include <r/session/RSession.hpp>
24 #include <r/session/RSessionState.hpp>
25
26 #include "REmbedded.hpp"
27 #include "RRestartContext.hpp"
28 #include "RStdCallbacks.hpp"
29 #include "RSuspend.hpp"
30
31 using namespace rstudio::core;
32
33 namespace rstudio {
34 namespace r {
35 namespace session {
36
37 namespace {
38
39 // session state path
40 FilePath s_suspendedSessionPath;
41
42 // client-state paths
43 FilePath s_clientStatePath;
44 FilePath s_projectClientStatePath;
45
46 // are in the middle of servicing a suspend request?
47 bool s_suspended = false;
48
saveSessionState(const RSuspendOptions & options,const FilePath & suspendedSessionPath,bool disableSaveCompression)49 bool saveSessionState(const RSuspendOptions& options,
50 const FilePath& suspendedSessionPath,
51 bool disableSaveCompression)
52 {
53 // notify client of serialization status
54 SerializationCallbackScope cb(kSerializationActionSuspendSession);
55
56 // suppress interrupts which occur during saving
57 r::exec::IgnoreInterruptsScope ignoreInterrupts;
58
59 // save
60 if (options.saveMinimal)
61 {
62 // save minimal
63 return r::session::state::saveMinimal(suspendedSessionPath,
64 options.saveWorkspace);
65
66 }
67 else
68 {
69 return r::session::state::save(suspendedSessionPath,
70 utils::isServerMode(),
71 options.excludePackages,
72 disableSaveCompression,
73 options.ephemeralEnvVars);
74 }
75 }
76
77 } // anonymous namespace
78
setSuspendPaths(const FilePath & suspendedSessionPath,const FilePath & clientStatePath,const FilePath & projectClientStatePath)79 void setSuspendPaths(const FilePath& suspendedSessionPath,
80 const FilePath& clientStatePath,
81 const FilePath& projectClientStatePath)
82 {
83 s_suspendedSessionPath = suspendedSessionPath;
84 s_clientStatePath = clientStatePath;
85 s_projectClientStatePath = projectClientStatePath;
86
87 }
88
suspendedSessionPath()89 FilePath suspendedSessionPath()
90 {
91 return s_suspendedSessionPath;
92 }
93
suspend(const RSuspendOptions & options,const FilePath & suspendedSessionPath,bool disableSaveCompression,bool force)94 bool suspend(const RSuspendOptions& options,
95 const FilePath& suspendedSessionPath,
96 bool disableSaveCompression,
97 bool force)
98 {
99 // validate that force == true if disableSaveCompression is specified
100 // this is because save compression is disabled and the previous options
101 // are not restored, so it is only suitable to use this when we know
102 // the process is going to go away completely
103 if (disableSaveCompression)
104 BOOST_ASSERT(force == true);
105
106 // commit all client state
107 saveClientState(ClientStateCommitAll);
108
109 // if we are saving minimal then clear the graphics device
110 if (options.saveMinimal)
111 {
112 r::session::graphics::display().clear();
113 }
114
115 // save the session state. errors are handled internally and reported
116 // directly to the end user and written to the server log.
117 bool suspend = saveSessionState(options,
118 suspendedSessionPath,
119 disableSaveCompression);
120
121 // if we failed to save the data and are being forced then warn user
122 if (!suspend && force)
123 {
124 reportAndLogWarning("Forcing suspend of process in spite of all session "
125 "data not being fully saved.");
126 suspend = true;
127 }
128
129 // only continue with exiting the process if we actually succeed in saving
130 if(suspend)
131 {
132 // set suspended flag so cleanup code can act accordingly
133 s_suspended = true;
134
135 // call suspend hook
136 rCallbacks().suspended(options);
137
138 // clean up but don't save workspace or runLast because we have
139 // been suspended
140 RCleanUp(SA_NOSAVE, options.status, FALSE);
141
142 // keep compiler happy (this line will never execute)
143 return true;
144 }
145 else
146 {
147 return false;
148 }
149 }
150
suspend(bool force,int status,const std::string & ephemeralEnvVars)151 bool suspend(bool force, int status, const std::string& ephemeralEnvVars)
152 {
153 return suspend(RSuspendOptions(status, ephemeralEnvVars),
154 s_suspendedSessionPath,
155 false,
156 force);
157 }
158
suspendForRestart(const RSuspendOptions & options)159 void suspendForRestart(const RSuspendOptions& options)
160 {
161 suspend(options,
162 RestartContext::createSessionStatePath(utils::scopedScratchPath(),
163 utils::sessionPort()),
164 true, // disable save compression
165 true); // force suspend
166 }
167
SerializationCallbackScope(int action,const FilePath & targetPath)168 SerializationCallbackScope::SerializationCallbackScope(int action,
169 const FilePath& targetPath)
170 {
171 rCallbacks().serialization(action, targetPath);
172 }
173
~SerializationCallbackScope()174 SerializationCallbackScope::~SerializationCallbackScope()
175 {
176 try {
177 rCallbacks().serialization(kSerializationActionCompleted,
178 FilePath());
179 } catch(...) {}
180 }
181
suspended()182 bool suspended()
183 {
184 return s_suspended;
185 }
186
saveClientState(ClientStateCommitType commitType)187 void saveClientState(ClientStateCommitType commitType)
188 {
189 using namespace r::session;
190
191 // save client state (note we don't explicitly restore this
192 // in restoreWorkingState, rather it is restored during
193 // initialize() so that the client always has access to it when
194 // for client_init)
195 r::session::clientState().commit(commitType,
196 s_clientStatePath,
197 s_projectClientStatePath);
198 }
199
200 namespace utils
201 {
202
clientStatePath()203 core::FilePath clientStatePath()
204 {
205 return s_clientStatePath;
206 }
207
projectClientStatePath()208 core::FilePath projectClientStatePath()
209 {
210 return s_projectClientStatePath;
211 }
212
suspendedSessionPath()213 core::FilePath suspendedSessionPath()
214 {
215 return s_suspendedSessionPath;
216 }
217
218 } // namespace utils
219
220 } // namespace session
221 } // namespace r
222 } // namespace rstudio
223