source: mainline/uspace/lib/c/generic/async_rel.c@ df908b3

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since df908b3 was ddd7118, checked in by Jakub Jermar <jakub@…>, 15 years ago

Add simple support for IPC relations to improve support for concurrent IPC
requests.

  • Property mode set to 100644
File size: 10.0 KB
Line 
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 relation support for the async framework.
37 *
38 * By the term "relation", we mean a logical data path between a client and a
39 * server over which the client can send multiple, potentially blocking,
40 * requests to the server.
41 *
42 * Clients and servers are naturally connected using IPC phones, thus an IPC
43 * phone represents a connection between a client and a server. In one
44 * connection, there can be many relations.
45 *
46 * Relations are useful in situations in which there is only one IPC connection
47 * between the client and the server, but the client wants to be able to make
48 * multiple parallel requests. Using only a single phone and without any other
49 * provisions, all requests would have to be serialized. On the other hand, the
50 * client can make as many parallel requests as there are active relations.
51 *
52 * There are several possible implementations of relations. This implementation
53 * uses additional phones to represent relations. Using phones both for the
54 * primary connection and also for its relations has several advantages:
55 *
56 * - to make a series of requests over a relation, the client can continue to
57 * use the existing async framework APIs
58 * - the server supports relations by the virtue of spawning a new connection
59 * fibril, just as it does for every new connection even without relations
60 * - the implementation is pretty straightforward; a very naive implementation
61 * would be to make each request using a fresh phone (that is what we have
62 * done in the past); a slightly better approach would be to cache connected
63 * phones so that they can be reused by a later relation within the same
64 * connection (that is what this implementation does)
65 *
66 * The main disadvantages of using phones to represent relations are:
67 *
68 * - if there are too many relations (even cached ones), the task may hit its
69 * limit on the maximum number of connected phones, which could prevent the
70 * task from making new IPC connections to other tasks
71 * - if there are too many IPC connections already, it may be impossible to
72 * create a relation by connecting a new phone thanks to the task's limit on
73 * the maximum number of connected phones
74 *
75 * These problems can be helped by increasing the limit on the maximum number of
76 * connected phones to some reasonable value and by limiting the number of
77 * phones cached to some fraction of this limit.
78 *
79 * The cache itself has a mechanism to close some number of unused phones if a
80 * new phone cannot be connected, but the outter world currently does not have a
81 * way to ask the phone cache to shrink.
82 *
83 * To minimize the confusion stemming from the fact that we use phones for two
84 * things (the primary IPC connection and also each relation), this file makes
85 * the distinction by using the term 'key phone' for the former and 'relation
86 * phone' for the latter. Under the hood, all phones remain equal, of course.
87 *
88 * There is a small inefficiency in that the cache repeatedly allocates and
89 * deallocated the rel_node_t structures when in fact it could keep the
90 * allocated structures around and reuse them later. But such a solution would
91 * be effectively implementing a poor man's slab allocator while it would be
92 * better to have the slab allocator ported to uspace so that everyone could
93 * benefit from it.
94 */
95
96#include <async_rel.h>
97#include <ipc/ipc.h>
98#include <fibril_synch.h>
99#include <adt/list.h>
100#include <adt/hash_table.h>
101#include <malloc.h>
102#include <errno.h>
103#include <assert.h>
104
105#define KEY_NODE_HASH_BUCKETS 16
106
107typedef struct {
108 link_t link; /**< Key node hash table link. */
109 int key_phone; /**< The phone serving as a key. */
110 link_t rel_head; /**< List of open relation phones. */
111} key_node_t;
112
113typedef struct {
114 link_t rel_link; /**< Link for the list of relation phones. */
115 link_t global_link; /**< Link for the global list of phones. */
116 int rel_phone; /**< Connected relation phone. */
117} rel_node_t;
118
119/**
120 * Mutex protecting the global_rel_head list and the key_node_hash hash table.
121 */
122static fibril_mutex_t async_rel_mutex;
123
124/**
125 * List of all currently unused relation phones.
126 */
127static LIST_INITIALIZE(global_rel_head);
128
129/**
130 * Hash table containing lists of available relation phones for all key
131 * phones.
132 */
133static hash_table_t key_node_hash;
134
135static hash_index_t kn_hash(unsigned long *key)
136{
137 return *key % KEY_NODE_HASH_BUCKETS;
138}
139
140static int kn_compare(unsigned long *key, hash_count_t keys, link_t *item)
141{
142 key_node_t *knp = hash_table_get_instance(item, key_node_t, link);
143
144 return *key == (unsigned long) knp->key_phone;
145}
146
147static void kn_remove_callback(link_t *item)
148{
149}
150
151static hash_table_operations_t key_node_hash_ops = {
152 .hash = kn_hash,
153 .compare = kn_compare,
154 .remove_callback = kn_remove_callback
155};
156
157/** Initialize the async_rel subsystem.
158 *
159 * Needs to be called prior to any other interface in this file.
160 */
161int async_rel_init(void)
162{
163 fibril_mutex_initialize(&async_rel_mutex);
164 list_initialize(&global_rel_head);
165 return hash_table_create(&key_node_hash, KEY_NODE_HASH_BUCKETS, 1,
166 &key_node_hash_ops);
167}
168
169static void key_node_initialize(key_node_t *knp)
170{
171 link_initialize(&knp->link);
172 knp->key_phone = -1;
173 list_initialize(&knp->rel_head);
174}
175
176static void rel_node_initialize(rel_node_t *rnp)
177{
178 link_initialize(&rnp->rel_link);
179 link_initialize(&rnp->global_link);
180 rnp->rel_phone = -1;
181}
182
183/** Create a new relation for a connection represented by a key phone.
184 *
185 * @param key_phone Phone representing the connection.
186 * @return Phone representing the new relation or a negative error
187 * code.
188 */
189int async_relation_create(int key_phone)
190{
191 unsigned long key = (unsigned long) key_phone;
192 link_t *lnk;
193 key_node_t *knp;
194 rel_node_t *rnp;
195 int rel_phone;
196
197 fibril_mutex_lock(&async_rel_mutex);
198 lnk = hash_table_find(&key_node_hash, &key);
199 if (!lnk) {
200 /*
201 * The key node was not found in the hash table. Try to allocate
202 * and hash in a new one.
203 */
204 knp = (key_node_t *) malloc(sizeof(key_node_t));
205 if (!knp) {
206 /*
207 * As a possible improvement, we could make a one-time
208 * attempt to create a phone without trying to add the
209 * key node into the hash.
210 */
211 fibril_mutex_unlock(&async_rel_mutex);
212 return ENOMEM;
213 }
214 key_node_initialize(knp);
215 knp->key_phone = key_phone;
216 hash_table_insert(&key_node_hash, &key, &knp->link);
217 } else {
218 /*
219 * Found the key node.
220 */
221 knp = hash_table_get_instance(lnk, key_node_t, link);
222 }
223
224 if (!list_empty(&knp->rel_head)) {
225 /*
226 * There are available relation phones for the key phone.
227 */
228 rnp = list_get_instance(knp->rel_head.next, rel_node_t,
229 rel_link);
230 list_remove(&rnp->rel_link);
231 list_remove(&rnp->global_link);
232
233 rel_phone = rnp->rel_phone;
234 free(rnp);
235 } else {
236 /*
237 * There are no available relation phones for the key phone.
238 * Make a one-time attempt to connect a new relation phone.
239 */
240retry:
241 rel_phone = ipc_connect_me_to(key_phone, 0, 0, 0);
242 if (rel_phone >= 0) {
243 /* success, do nothing */
244 } else if (!list_empty(&global_rel_head)) {
245 /*
246 * We did not manage to connect a new phone. But we can
247 * try to hangup some currently unused phones and try
248 * again.
249 */
250 rnp = list_get_instance(global_rel_head.next,
251 rel_node_t, global_link);
252 list_remove(&rnp->global_link);
253 list_remove(&rnp->rel_link);
254 rel_phone = rnp->rel_phone;
255 free(rnp);
256 ipc_hangup(rel_phone);
257 goto retry;
258 } else {
259 /*
260 * This is unfortunate. We failed both to find a cached
261 * phone or to create a new one even after cleaning up
262 * the cache. This is most likely due to too many key
263 * phones being kept connected.
264 */
265 rel_phone = ELIMIT;
266 }
267 }
268
269 fibril_mutex_unlock(&async_rel_mutex);
270 return rel_phone;
271}
272
273/** Destroy a relation.
274 *
275 * @param key_phone Phone representing the connection.
276 * @param rel_phone Phone representing the relation within the connection.
277 */
278void async_relation_destroy(int key_phone, int rel_phone)
279{
280 unsigned long key = (unsigned long) key_phone;
281 key_node_t *knp;
282 rel_node_t *rnp;
283 link_t *lnk;
284
285 fibril_mutex_lock(&async_rel_mutex);
286 lnk = hash_table_find(&key_node_hash, &key);
287 assert(lnk);
288 knp = hash_table_get_instance(lnk, key_node_t, link);
289 rnp = (rel_node_t *) malloc(sizeof(rel_node_t));
290 if (!rnp) {
291 /*
292 * Being unable to remember the connected relation phone here
293 * means that we simply hangup.
294 */
295 fibril_mutex_unlock(&async_rel_mutex);
296 ipc_hangup(rel_phone);
297 return;
298 }
299 rel_node_initialize(rnp);
300 rnp->rel_phone = rel_phone;
301 list_append(&rnp->rel_link, &knp->rel_head);
302 list_append(&rnp->global_link, &global_rel_head);
303 fibril_mutex_unlock(&async_rel_mutex);
304}
305
306/** @}
307 */
Note: See TracBrowser for help on using the repository browser.