2
* Copyright (c) 2010 Jakub Jermar
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
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.
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.
36
* This file implements simple session support for the async framework.
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.
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).
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).
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:
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)
69
* The main disadvantages of using phones to represent sessions are:
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
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.
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.
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,
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
100
#include <async_sess.h>
101
#include <fibril_synch.h>
102
#include <adt/list.h>
103
#include <adt/hash_table.h>
107
#include "private/async_sess.h"
109
/** An inactive open connection. */
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. */
117
* Mutex protecting the inactive_conn_head list, the session list and the
118
* avail_phone condition variable.
120
static fibril_mutex_t async_sess_mutex;
123
* List of all currently inactive connections.
125
static LIST_INITIALIZE(inactive_conn_head);
128
* List of all open sessions.
130
static LIST_INITIALIZE(session_list_head);
133
* Condition variable used to wait for a phone to become available.
135
static FIBRIL_CONDVAR_INITIALIZE(avail_phone_cv);
137
/** Initialize the async_sess subsystem.
139
* Needs to be called prior to any other interface in this file.
142
void __async_sess_init(void)
144
fibril_mutex_initialize(&async_sess_mutex);
145
list_initialize(&inactive_conn_head);
146
list_initialize(&session_list_head);
149
/** Create a session.
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.
155
* This function always succeeds.
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.).
164
void async_session_create(async_sess_t *sess, int phone, sysarg_t arg1)
166
sess->sess_phone = phone;
167
sess->connect_arg1 = arg1;
168
list_initialize(&sess->conn_head);
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);
176
/** Destroy a session.
178
* Dismantle session structure @a sess and release any resources (connections)
179
* held by the session.
181
* @param sess Session to destroy.
183
void async_session_destroy(async_sess_t *sess)
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);
192
/* We did not connect the phone so we do not hang it up either. */
193
sess->sess_phone = -1;
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,
200
list_remove(&conn->sess_link);
201
list_remove(&conn->global_link);
203
async_hangup(conn->data_phone);
207
fibril_condvar_broadcast(&avail_phone_cv);
210
static void conn_node_initialize(conn_node_t *conn)
212
link_initialize(&conn->sess_link);
213
link_initialize(&conn->global_link);
214
conn->data_phone = -1;
217
/** Start new exchange in a session.
219
* @param sess_phone Session.
220
* @return Phone representing the new exchange or a negative error
223
int async_exchange_begin(async_sess_t *sess)
228
fibril_mutex_lock(&async_sess_mutex);
230
if (!list_empty(&sess->conn_head)) {
232
* There are inactive connections in the session.
234
conn = list_get_instance(sess->conn_head.next, conn_node_t,
236
list_remove(&conn->sess_link);
237
list_remove(&conn->global_link);
239
data_phone = conn->data_phone;
243
* There are no available connections in the session.
244
* Make a one-time attempt to connect a new data phone.
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)) {
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.
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;
263
async_hangup(data_phone);
267
* Wait for a phone to become available.
269
fibril_condvar_wait(&avail_phone_cv, &async_sess_mutex);
274
fibril_mutex_unlock(&async_sess_mutex);
278
/** Finish an exchange.
280
* @param sess Session.
281
* @param data_phone Phone representing the exchange within the session.
283
void async_exchange_end(async_sess_t *sess, int data_phone)
287
fibril_mutex_lock(&async_sess_mutex);
288
fibril_condvar_signal(&avail_phone_cv);
289
conn = (conn_node_t *) malloc(sizeof(conn_node_t));
292
* Being unable to remember the connected data phone here
293
* means that we simply hang up.
295
async_hangup(data_phone);
296
fibril_mutex_unlock(&async_sess_mutex);
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);