~marcustomlinson/unity-scopes-api/scope_process_lifetime

« back to all changes in this revision

Viewing changes to src/scopes/internal/RegistryObject.cpp

  • Committer: Marcus Tomlinson
  • Date: 2014-03-13 07:55:44 UTC
  • Revision ID: marcus.tomlinson@canonical.com-20140313075544-5tg03wrhs1swna96
Reduced mutexes in RegistryObject::ScopeProcess to a single process_mutex_ to better safety-proof and simplify concurrent request handling.

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
        }
57
57
        catch(std::exception const& e)
58
58
        {
59
 
            cerr << "RegistryObject::~RegistryObject: " << e.what() << endl;
60
 
        }
61
 
    }
62
 
 
63
 
    // wait for scope processes to terminate
64
 
    for (auto& scope_process : scope_processes_)
65
 
    {
66
 
        if (!scope_process.second.wait_for_state(ScopeProcess::Stopped, 1000))
67
 
        {
68
 
            cerr << "RegistryObject::~RegistryObject: Scope: \"" << scope_process.second.scope_id()
69
 
                 << "\" is taking longer than expected to terminate (This process is likely to close upon"
70
 
                 << " termination of the parent application)." << endl;
 
59
            cerr << "RegistryObject::~RegistryObject(): " << e.what() << endl;
71
60
        }
72
61
    }
73
62
 
131
120
    // If the id is empty, it was sent as empty by the remote client.
132
121
    if (scope_id.empty())
133
122
    {
134
 
        throw unity::InvalidArgumentException("Registry: Cannot locate scope with empty id");
 
123
        throw unity::InvalidArgumentException("Registry::locate(): Cannot locate scope with empty id");
135
124
    }
136
125
 
137
126
    auto scope_it = scopes_.find(scope_id);
138
127
    if (scope_it == scopes_.end())
139
128
    {
140
 
        throw NotFoundException("Tried to locate unknown local scope", scope_id);
 
129
        throw NotFoundException("Registry::locate(): Tried to locate unknown local scope", scope_id);
141
130
    }
142
131
 
143
132
    auto proc_it = scope_processes_.find(scope_id);
144
133
    if (proc_it == scope_processes_.end())
145
134
    {
146
 
        throw NotFoundException("Tried to exec unknown local scope", scope_id);
 
135
        throw NotFoundException("Registry::locate(): Tried to exec unknown local scope", scope_id);
147
136
    }
148
137
 
149
138
    proc_it->second.exec();
162
151
    }
163
152
    if (scope_id.find('/') != std::string::npos)
164
153
    {
165
 
        throw unity::InvalidArgumentException("Registry: Cannot create a scope with a slash in its id");
 
154
        throw unity::InvalidArgumentException("RegistryObject::add_local_scope(): Cannot create a scope with '/' in its id");
166
155
    }
167
156
 
168
157
    if (scopes_.find(scope_id) != scopes_.end())
224
213
    return exec_data_.scope_id;
225
214
}
226
215
 
227
 
RegistryObject::ScopeProcess::ProcessState RegistryObject::ScopeProcess::state() const
228
 
{
229
 
    std::lock_guard<std::mutex> lock(state_mutex_);
230
 
    return state_;
231
 
}
232
 
 
233
216
bool RegistryObject::ScopeProcess::wait_for_state(ProcessState state, int timeout_ms) const
234
217
{
235
 
    std::unique_lock<std::mutex> lock(state_mutex_);
236
 
 
237
 
    // keep track of time left as process can undergo multiple state changes
238
 
    // before reaching the state we want
239
 
    int time_left = timeout_ms;
240
 
    while (state_ != state && time_left > 0)
241
 
    {
242
 
        auto start = std::chrono::high_resolution_clock::now();
243
 
        state_change_cond_.wait_for(lock, std::chrono::milliseconds(time_left));
244
 
 
245
 
        // update time left
246
 
        time_left -= std::chrono::duration_cast<std::chrono::milliseconds>(
247
 
                     std::chrono::high_resolution_clock::now() - start).count();
248
 
    }
249
 
 
250
 
    return state_ == state;
 
218
    std::unique_lock<std::mutex> lock(process_mutex_);
 
219
    return in_lock_wait_for_state(lock, state, timeout_ms);
251
220
}
252
221
 
253
222
void RegistryObject::ScopeProcess::exec()
254
223
{
 
224
    std::unique_lock<std::mutex> lock(process_mutex_);
 
225
 
255
226
    // 1. check if the scope is running.
256
227
    //  1.1. if scope already running, return.
257
 
    if (state() == ScopeProcess::Running)
 
228
    if (state_ == ScopeProcess::Running)
258
229
    {
259
230
        return;
260
231
    }
261
232
    //  1.2. if scope running but is “stopping”, wait for it to stop / kill it.
262
 
    else if (state() == ScopeProcess::Stopping)
 
233
    else if (state_ == ScopeProcess::Stopping)
263
234
    {
264
 
        if (!wait_for_state(ScopeProcess::Stopped, 1000))
 
235
        if (!in_lock_wait_for_state(lock, ScopeProcess::Stopped, 1000))
265
236
        {
266
 
            cerr << "RegistryObject::ScopeProcess: Force killing process. Scope: \"" << exec_data_.scope_id
267
 
                 << "\" took too long to stop." << endl;
268
 
            kill();
 
237
            cerr << "RegistryObject::ScopeProcess::exec(): Force killing process. Scope: \""
 
238
                 << exec_data_.scope_id << "\" took too long to stop." << endl;
 
239
            in_lock_kill(lock);
269
240
        }
270
241
    }
271
242
 
272
243
    // 2. exec the scope.
273
 
    update_state(Starting);
 
244
    in_lock_update_state(Starting);
274
245
 
275
246
    const std::string program{exec_data_.scoperunner_path};
276
247
    const std::vector<std::string> argv = {exec_data_.runtime_config, exec_data_.scope_config};
282
253
    });
283
254
 
284
255
    {
285
 
        std::lock_guard<std::mutex> lock(process_mutex_);
286
256
        process_ = core::posix::exec(program, argv, env, core::posix::StandardStream::empty);
287
257
        if (process_.pid() <= 0)
288
258
        {
289
259
            process_ = core::posix::ChildProcess::invalid();
290
 
            update_state(Stopped);
291
 
            throw unity::ResourceException("RegistryObject::ScopeProcess: Failed to exec scope via command: \"" +
292
 
                                           exec_data_.scoperunner_path + " " + exec_data_.runtime_config + " " +
293
 
                                           exec_data_.scope_config + "\"");
 
260
            in_lock_update_state(Stopped);
 
261
            throw unity::ResourceException("RegistryObject::ScopeProcess::exec(): Failed to exec scope via command: \""
 
262
                                           + exec_data_.scoperunner_path + " " + exec_data_.runtime_config + " "
 
263
                                           + exec_data_.scope_config + "\"");
294
264
        }
295
265
    }
296
266
 
297
267
    ///! TODO: This should not be here. A ready signal from the scope should trigger "running".
298
 
    update_state(Running);
 
268
    in_lock_update_state(Running);
299
269
 
300
270
    // 3. wait for scope to be "running".
301
271
    //  3.1. when ready, return.
302
272
    //  3.2. OR if timeout, kill process and throw.
303
 
    if (!wait_for_state(ScopeProcess::Running, 1000))
 
273
    if (!in_lock_wait_for_state(lock, ScopeProcess::Running, 1000))
304
274
    {
305
 
        kill();
306
 
        throw unity::ResourceException("RegistryObject::ScopeProcess: exec() aborted. Scope: \""
 
275
        in_lock_kill(lock);
 
276
        throw unity::ResourceException("RegistryObject::ScopeProcess::exec(): exec aborted. Scope: \""
307
277
                                       + exec_data_.scope_id + "\" took too long to start.");
308
278
    }
309
279
 
315
285
 
316
286
void RegistryObject::ScopeProcess::kill()
317
287
{
318
 
    // if scope already stopped, return.
319
 
    if (state() == ScopeProcess::Stopped)
320
 
    {
321
 
        return;
322
 
    }
323
 
 
324
 
    try
325
 
    {
326
 
        std::lock_guard<std::mutex> lock(process_mutex_);
327
 
        process_.send_signal_or_throw(core::posix::Signal::sig_kill);
328
 
    }
329
 
    catch (std::exception const&)
330
 
    {
331
 
        cerr << "RegistryObject::ScopeProcess: Failed to kill scope: \""
332
 
             << exec_data_.scope_id << "\"" << endl;
333
 
        throw;
334
 
    }
 
288
    std::unique_lock<std::mutex> lock(process_mutex_);
 
289
    in_lock_kill(lock);
335
290
}
336
291
 
337
292
bool RegistryObject::ScopeProcess::on_process_death(pid_t pid)
341
296
    // check if this is the process reported to have died
342
297
    if (pid == process_.pid())
343
298
    {
344
 
        cout << "RegistryObject::ScopeProcess: Process for scope: \"" << exec_data_.scope_id << "\" terminated" << endl;
 
299
        cout << "RegistryObject::ScopeProcess::on_process_death(): Process for scope: \"" << exec_data_.scope_id
 
300
             << "\" terminated" << endl;
345
301
        process_ = core::posix::ChildProcess::invalid();
346
 
        update_state(Stopped);
 
302
        in_lock_update_state(Stopped);
347
303
        return true;
348
304
    }
349
305
 
350
306
    return false;
351
307
}
352
308
 
353
 
void RegistryObject::ScopeProcess::update_state(ProcessState state)
 
309
void RegistryObject::ScopeProcess::in_lock_update_state(ProcessState state)
354
310
{
355
 
    std::lock_guard<std::mutex> lock(state_mutex_);
356
311
    state_ = state;
357
312
    state_change_cond_.notify_all();
358
313
}
359
314
 
 
315
bool RegistryObject::ScopeProcess::in_lock_wait_for_state(std::unique_lock<std::mutex>& lock,
 
316
                                                          ProcessState state, int timeout_ms) const
 
317
{
 
318
    // keep track of time left as process can undergo multiple state changes
 
319
    // before reaching the state we want
 
320
    int time_left = timeout_ms;
 
321
    while (state_ != state && time_left > 0)
 
322
    {
 
323
        auto start = std::chrono::high_resolution_clock::now();
 
324
        state_change_cond_.wait_for(lock, std::chrono::milliseconds(time_left));
 
325
 
 
326
        // update time left
 
327
        time_left -= std::chrono::duration_cast<std::chrono::milliseconds>(
 
328
                     std::chrono::high_resolution_clock::now() - start).count();
 
329
    }
 
330
 
 
331
    return state_ == state;
 
332
}
 
333
 
 
334
void RegistryObject::ScopeProcess::in_lock_kill(std::unique_lock<std::mutex>& lock)
 
335
{
 
336
    if (state_ == Stopped)
 
337
    {
 
338
        return;
 
339
    }
 
340
 
 
341
    try
 
342
    {
 
343
        process_.send_signal_or_throw(core::posix::Signal::sig_kill);
 
344
 
 
345
        if (!in_lock_wait_for_state(lock, ScopeProcess::Stopped, 1000))
 
346
        {
 
347
            throw unity::ResourceException("Scope: \"" + exec_data_.scope_id
 
348
                 + "\" is taking longer than expected to terminate (This process is "
 
349
                 + "likely to close upon termination of the parent application).");
 
350
        }
 
351
    }
 
352
    catch (std::exception const&)
 
353
    {
 
354
        cerr << "RegistryObject::ScopeProcess::in_lock_kill(): Failed to kill scope: \""
 
355
             << exec_data_.scope_id << "\"" << endl;
 
356
        throw;
 
357
    }
 
358
}
 
359
 
360
360
} // namespace internal
361
361
 
362
362
} // namespace scopes