summaryrefslogtreecommitdiff
path: root/src/dbus/server/auto-term.h
blob: 15cf12608e78f72b73599a050ee5a71cb7566260 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
 * Copyright (C) 2011 Intel Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) version 3.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef AUTOTERM_H
#define AUTOTERM_H

#include <syncevo/SmartPtr.h>

#include <syncevo/declarations.h>
SE_BEGIN_CXX

/**
 * Automatic termination and track clients
 * The dbus server will automatic terminate once it is idle in a given time.
 * If any attached clients or connections, it never terminate.
 * Once no actives, timer is started to detect the time of idle.
 * Note that there will be less-than TERM_INTERVAL inaccuracy in seconds,
 * that's because we do check every TERM_INTERVAL seconds.
 */
class AutoTerm {
    GMainLoop *m_loop;
    bool &m_shutdownRequested;
    int m_refs;
    time_t m_interval;
    guint m_checkSource;
    time_t m_lastUsed;

    /**
     * This callback is called as soon as we might have to terminate.
     * If it finds that the server has been used in the meantime, it
     * will simply set another timeout and check again later.
     */
    static gboolean checkCallback(gpointer data) {
        AutoTerm *at = static_cast<AutoTerm*>(data);
        if (!at->m_refs) {
            // currently idle, but also long enough?
            time_t now = time(NULL);
            if (at->m_lastUsed + at->m_interval <= now) {
                // yes, shut down event loop and daemon
                SE_LOG_DEBUG(NULL, NULL, "terminating because not in use and idle for more than %ld seconds", (long)at->m_interval);
                at->m_shutdownRequested = true;
                g_main_loop_quit(at->getLoop());
            } else {
                // check again later
                SE_LOG_DEBUG(NULL, NULL, "not terminating because last used %ld seconds ago, check again in %ld seconds",
                             (long)(now - at->m_lastUsed),
                             (long)(at->m_lastUsed + at->m_interval - now));
                at->m_checkSource = g_timeout_add_seconds(at->m_lastUsed + at->m_interval - now,
                                                          checkCallback,
                                                          data);
            }
        } else {
            SE_LOG_DEBUG(NULL, NULL, "not terminating, not renewing timeout because busy");
        }
        // always remove the current timeout, its job is done
        return FALSE;
    }

public:
    /**
     * constructor
     * If interval is less than 0, it means 'unlimited' and never terminate
     */
    AutoTerm(GMainLoop *loop, bool &shutdownRequested, int interval) :
        m_loop(loop),
        m_shutdownRequested(shutdownRequested),
        m_refs(0),
        m_checkSource(0),
        m_lastUsed(0)
    {
        if (interval <= 0) {
            m_interval = 0;
            // increasing reference counts prevents shutdown forever
            ref();
        } else {
            m_interval = interval;
        }
        reset();
    }

    ~AutoTerm()
    {
        if (m_checkSource) {
            g_source_remove(m_checkSource);
        }
    }

    /** access to the GMainLoop. */
    GMainLoop *getLoop() { return m_loop; }

    //increase the actives objects
    void ref(int refs = 1) {
        m_refs += refs;
        reset();
    }

    //decrease the actives objects
    void unref(int refs = 1) {
        m_refs -= refs;
        if(m_refs <= 0) {
           m_refs = 0;
        }
        reset();
    }

    /**
     * To be called each time the server interacts with a client,
     * which includes adding or removing a client. If necessary,
     * this installs a timeout to stop the daemon when it has been
     * idle long enough.
     */
    void reset()
    {
        if (m_refs > 0) {
            // in use, don't need timeout
            if (m_checkSource) {
                SE_LOG_DEBUG(NULL, NULL, "deactivating idle termination because in use");
                g_source_remove(m_checkSource);
                m_checkSource = 0;
            }
        } else {
            // An already active timeout will trigger at the chosen time,
            // then notice that the server has been used in the meantime and
            // reset the timer. Therefore we don't have to remove it.
            m_lastUsed = time(NULL);
            if (!m_checkSource) {
                SE_LOG_DEBUG(NULL, NULL, "activating idle termination in %ld seconds because idle", m_interval);
                m_checkSource = g_timeout_add_seconds(m_interval,
                                                      checkCallback,
                                                      static_cast<gpointer>(this));
            }
        }
    }
};

SE_END_CXX

#endif // AUTOTERM_H