~jakub/helenos/ia64-revival

« back to all changes in this revision

Viewing changes to uspace/lib/c/generic/async_sess.c

  • Committer: Jakub Jermar
  • Date: 2011-04-13 14:45:41 UTC
  • mfrom: (527.1.397 main-clone)
  • Revision ID: jakub@jermar.eu-20110413144541-x0j3r1zxqhsljx1o
MergeĀ mainlineĀ changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (c) 2010 Jakub Jermar
 
3
 * All rights reserved.
 
4
 *
 
5
 * Redistribution and use in source and binary forms, with or without
 
6
 * modification, are permitted provided that the following conditions
 
7
 * are met:
 
8
 *
 
9
 * - Redistributions of source code must retain the above copyright
 
10
 *   notice, this list of conditions and the following disclaimer.
 
11
 * - Redistributions in binary form must reproduce the above copyright
 
12
 *   notice, this list of conditions and the following disclaimer in the
 
13
 *   documentation and/or other materials provided with the distribution.
 
14
 * - The name of the author may not be used to endorse or promote products
 
15
 *   derived from this software without specific prior written permission.
 
16
 *
 
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 
18
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 
19
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 
20
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 
21
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 
22
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 
26
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
27
 */
 
28
 
 
29
/** @addtogroup libc
 
30
 * @{
 
31
 */
 
32
/** @file
 
33
 */
 
34
 
 
35
/**
 
36
 * This file implements simple session support for the async framework.
 
37
 *
 
38
 * By the term 'session', we mean a logical data path between a client and a
 
39
 * server over which the client can perform multiple concurrent exchanges.
 
40
 * Each exchange consists of one or more requests (IPC calls) which can
 
41
 * be potentially blocking.
 
42
 *
 
43
 * Clients and servers are naturally connected using IPC phones, thus an IPC
 
44
 * phone represents a session between a client and a server. In one
 
45
 * session, there can be many outstanding exchanges. In the current
 
46
 * implementation each concurrent exchanges takes place over a different
 
47
 * connection (there can be at most one active exchage per connection).
 
48
 *
 
49
 * Sessions make it useful for a client or client API to support concurrent
 
50
 * requests, independent of the actual implementation. Sessions provide
 
51
 * an abstract interface to concurrent IPC communication. This is especially
 
52
 * useful for client API stubs that aim to be reentrant (i.e. that allow
 
53
 * themselves to be called from different fibrils and threads concurrently).
 
54
 *
 
55
 * There are several possible implementations of sessions. This implementation
 
56
 * uses additional phones to represent sessions. Using phones both for the
 
57
 * session and also for its exchages/connections has several advantages:
 
58
 *
 
59
 * - to make a series of exchanges over a session, the client can continue to
 
60
 *   use the existing async framework APIs
 
61
 * - the server supports sessions by the virtue of spawning a new connection
 
62
 *   fibril, just as it does for every new connection even without sessions
 
63
 * - the implementation is pretty straightforward; a very naive implementation
 
64
 *   would be to make each exchage using a fresh phone (that is what we
 
65
 *   have done in the past); a slightly better approach would be to cache
 
66
 *   connections so that they can be reused by a later exchange within
 
67
 *   the same session (that is what this implementation does)
 
68
 *
 
69
 * The main disadvantages of using phones to represent sessions are:
 
70
 *
 
71
 * - if there are too many exchanges (even cached ones), the task may hit its
 
72
 *   limit on the maximum number of connected phones, which could prevent the
 
73
 *   task from making new IPC connections to other tasks
 
74
 * - if there are too many IPC connections already, it may be impossible to
 
75
 *   create an exchange by connecting a new phone thanks to the task's limit on
 
76
 *   the maximum number of connected phones
 
77
 *
 
78
 * These problems can be alleviated by increasing the limit on the maximum
 
79
 * number of connected phones to some reasonable value and by limiting the number
 
80
 * of cached connections to some fraction of this limit.
 
81
 *
 
82
 * The cache itself has a mechanism to close some number of unused phones if a
 
83
 * new phone cannot be connected, but the outer world currently does not have a
 
84
 * way to ask the phone cache to shrink.
 
85
 *
 
86
 * To minimize the confusion stemming from the fact that we use phones for two
 
87
 * things (the session itself and also one for each data connection), this file
 
88
 * makes the distinction by using the term 'session phone' for the former and
 
89
 * 'data phone' for the latter. Under the hood, all phones remain equal,
 
90
 * of course.
 
91
 *
 
92
 * There is a small inefficiency in that the cache repeatedly allocates and
 
93
 * deallocates the conn_node_t structures when in fact it could keep the
 
94
 * allocated structures around and reuse them later. But such a solution would
 
95
 * be effectively implementing a poor man's slab allocator while it would be
 
96
 * better to have the slab allocator ported to uspace so that everyone could
 
97
 * benefit from it.
 
98
 */
 
99
 
 
100
#include <async_sess.h>
 
101
#include <fibril_synch.h>
 
102
#include <adt/list.h>
 
103
#include <adt/hash_table.h>
 
104
#include <malloc.h>
 
105
#include <errno.h>
 
106
#include <assert.h>
 
107
#include "private/async_sess.h"
 
108
 
 
109
/** An inactive open connection. */
 
110
typedef struct {
 
111
        link_t sess_link;       /**< Link for the session list of inactive connections. */
 
112
        link_t global_link;     /**< Link for the global list of inactive connections. */
 
113
        int data_phone;         /**< Connected data phone. */
 
114
} conn_node_t;
 
115
 
 
116
/**
 
117
 * Mutex protecting the inactive_conn_head list, the session list and the
 
118
 * avail_phone condition variable.
 
119
 */
 
120
static fibril_mutex_t async_sess_mutex;
 
121
 
 
122
/**
 
123
 * List of all currently inactive connections.
 
124
 */
 
125
static LIST_INITIALIZE(inactive_conn_head);
 
126
 
 
127
/**
 
128
 * List of all open sessions.
 
129
 */
 
130
static LIST_INITIALIZE(session_list_head);
 
131
 
 
132
/**
 
133
 * Condition variable used to wait for a phone to become available.
 
134
 */
 
135
static FIBRIL_CONDVAR_INITIALIZE(avail_phone_cv);
 
136
 
 
137
/** Initialize the async_sess subsystem.
 
138
 *
 
139
 * Needs to be called prior to any other interface in this file.
 
140
 *
 
141
 */
 
142
void __async_sess_init(void)
 
143
{
 
144
        fibril_mutex_initialize(&async_sess_mutex);
 
145
        list_initialize(&inactive_conn_head);
 
146
        list_initialize(&session_list_head);
 
147
}
 
148
 
 
149
/** Create a session.
 
150
 *
 
151
 * Session is a logical datapath from a client task to a server task.
 
152
 * One session can accomodate multiple concurrent exchanges. Here
 
153
 * @a phone is a phone connected to the desired server task.
 
154
 *
 
155
 * This function always succeeds.
 
156
 *
 
157
 * @param sess  Session structure provided by caller, will be filled in.
 
158
 * @param phone Phone connected to the desired server task.
 
159
 * @param arg1  Value to pass as first argument upon creating a new
 
160
 *              connection. Typical use is to identify a resource within
 
161
 *              the server that the caller wants to access (port ID,
 
162
 *              interface ID, device ID, etc.).
 
163
 */
 
164
void async_session_create(async_sess_t *sess, int phone, sysarg_t arg1)
 
165
{
 
166
        sess->sess_phone = phone;
 
167
        sess->connect_arg1 = arg1;
 
168
        list_initialize(&sess->conn_head);
 
169
        
 
170
        /* Add to list of sessions. */
 
171
        fibril_mutex_lock(&async_sess_mutex);
 
172
        list_append(&sess->sess_link, &session_list_head);
 
173
        fibril_mutex_unlock(&async_sess_mutex);
 
174
}
 
175
 
 
176
/** Destroy a session.
 
177
 *
 
178
 * Dismantle session structure @a sess and release any resources (connections)
 
179
 * held by the session.
 
180
 *
 
181
 * @param sess  Session to destroy.
 
182
 */
 
183
void async_session_destroy(async_sess_t *sess)
 
184
{
 
185
        conn_node_t *conn;
 
186
 
 
187
        /* Remove from list of sessions. */
 
188
        fibril_mutex_lock(&async_sess_mutex);
 
189
        list_remove(&sess->sess_link);
 
190
        fibril_mutex_unlock(&async_sess_mutex);
 
191
 
 
192
        /* We did not connect the phone so we do not hang it up either. */
 
193
        sess->sess_phone = -1;
 
194
 
 
195
        /* Tear down all data connections. */
 
196
        while (!list_empty(&sess->conn_head)) {
 
197
                conn = list_get_instance(sess->conn_head.next, conn_node_t,
 
198
                    sess_link);
 
199
 
 
200
                list_remove(&conn->sess_link);
 
201
                list_remove(&conn->global_link);
 
202
                
 
203
                async_hangup(conn->data_phone);
 
204
                free(conn);
 
205
        }
 
206
        
 
207
        fibril_condvar_broadcast(&avail_phone_cv);
 
208
}
 
209
 
 
210
static void conn_node_initialize(conn_node_t *conn)
 
211
{
 
212
        link_initialize(&conn->sess_link);
 
213
        link_initialize(&conn->global_link);
 
214
        conn->data_phone = -1;
 
215
}
 
216
 
 
217
/** Start new exchange in a session.
 
218
 *
 
219
 * @param sess_phone    Session.
 
220
 * @return              Phone representing the new exchange or a negative error
 
221
 *                      code.
 
222
 */
 
223
int async_exchange_begin(async_sess_t *sess)
 
224
{
 
225
        conn_node_t *conn;
 
226
        int data_phone;
 
227
 
 
228
        fibril_mutex_lock(&async_sess_mutex);
 
229
 
 
230
        if (!list_empty(&sess->conn_head)) {
 
231
                /*
 
232
                 * There are inactive connections in the session.
 
233
                 */
 
234
                conn = list_get_instance(sess->conn_head.next, conn_node_t,
 
235
                    sess_link);
 
236
                list_remove(&conn->sess_link);
 
237
                list_remove(&conn->global_link);
 
238
                
 
239
                data_phone = conn->data_phone;
 
240
                free(conn);
 
241
        } else {
 
242
                /*
 
243
                 * There are no available connections in the session.
 
244
                 * Make a one-time attempt to connect a new data phone.
 
245
                 */
 
246
retry:
 
247
                data_phone = async_connect_me_to(sess->sess_phone,
 
248
                    sess->connect_arg1, 0, 0);
 
249
                if (data_phone >= 0) {
 
250
                        /* success, do nothing */
 
251
                } else if (!list_empty(&inactive_conn_head)) {
 
252
                        /*
 
253
                         * We did not manage to connect a new phone. But we can
 
254
                         * try to close some of the currently inactive
 
255
                         * connections in other sessions and try again.
 
256
                         */
 
257
                        conn = list_get_instance(inactive_conn_head.next,
 
258
                            conn_node_t, global_link);
 
259
                        list_remove(&conn->global_link);
 
260
                        list_remove(&conn->sess_link);
 
261
                        data_phone = conn->data_phone;
 
262
                        free(conn);
 
263
                        async_hangup(data_phone);
 
264
                        goto retry;
 
265
                } else {
 
266
                        /*
 
267
                         * Wait for a phone to become available.
 
268
                         */
 
269
                        fibril_condvar_wait(&avail_phone_cv, &async_sess_mutex);
 
270
                        goto retry;
 
271
                }
 
272
        }
 
273
 
 
274
        fibril_mutex_unlock(&async_sess_mutex);
 
275
        return data_phone;
 
276
}
 
277
 
 
278
/** Finish an exchange.
 
279
 *
 
280
 * @param sess          Session.
 
281
 * @param data_phone    Phone representing the exchange within the session.
 
282
 */
 
283
void async_exchange_end(async_sess_t *sess, int data_phone)
 
284
{
 
285
        conn_node_t *conn;
 
286
 
 
287
        fibril_mutex_lock(&async_sess_mutex);
 
288
        fibril_condvar_signal(&avail_phone_cv);
 
289
        conn = (conn_node_t *) malloc(sizeof(conn_node_t));
 
290
        if (!conn) {
 
291
                /*
 
292
                 * Being unable to remember the connected data phone here
 
293
                 * means that we simply hang up.
 
294
                 */
 
295
                async_hangup(data_phone);
 
296
                fibril_mutex_unlock(&async_sess_mutex);
 
297
                return;
 
298
        }
 
299
 
 
300
        conn_node_initialize(conn);
 
301
        conn->data_phone = data_phone;
 
302
        list_append(&conn->sess_link, &sess->conn_head);
 
303
        list_append(&conn->global_link, &inactive_conn_head);
 
304
        fibril_mutex_unlock(&async_sess_mutex);
 
305
}
 
306
 
 
307
/** @}
 
308
 */