summaryrefslogtreecommitdiff
path: root/libports/interrupt-on-notify.c
blob: b358e840122cfd5252c2c152558c043b6e8e3443 (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/* Mark an rpc to be interrupted when a port dies

   Copyright (C) 1995, 96, 99 Free Software Foundation, Inc.

   Written by Miles Bader <miles@gnu.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2, or (at
   your option) any later version.

   This program 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
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "ports.h"
#include <assert.h>

/* Arrange for hurd_cancel to be called on RPC's thread if OBJECT gets notified
   that any of the things in COND have happened to PORT.  RPC should be an
   rpc on OBJECT.  */
error_t
ports_interrupt_rpc_on_notification (void *object,
				     struct rpc_info *rpc,
				     mach_port_t port, mach_msg_id_t what)
{
  int req_notify;
  struct ports_notify *pn;
  struct rpc_notify *new_req, *req;
  struct port_info *pi = object;

  pthread_mutex_lock (&_ports_lock);

  if (! MACH_PORT_VALID (port))
    /* PORT is already dead or bogus, so interrupt the rpc immediately.  */
    {
      hurd_thread_cancel (rpc->thread);
      pthread_mutex_unlock (&_ports_lock);
      return 0;
    }

  new_req = _ports_free_rpc_notifies;
  if (new_req)
    /* We got a req off the free list.  */
    _ports_free_rpc_notifies = new_req->next;
  else
    /* No free notify structs, allocate one; it's expected that 99% of the
       time we'll add a new structure, so we malloc while we don't have the
       lock, and free it if we're wrong.  */
    {
      pthread_mutex_unlock (&_ports_lock); /* Don't hold the lock during malloc. */
      new_req = malloc (sizeof (struct rpc_notify));
      if (! new_req)
	return ENOMEM;
      pthread_mutex_lock (&_ports_lock);
    }

  /* Find any existing entry for PORT/WHAT.  */
  for (pn = _ports_notifications; pn; pn = pn->next)
    if (pn->port == port && pn->what == what)
      break;

  if (! pn)
    /* A notification on a new port.  */
    {
      pn = _ports_free_ports_notifies;

      if (pn)
	_ports_free_ports_notifies = pn->next;
      else
	{
	  pn = malloc (sizeof (struct ports_notify));
	  if (! pn)
	    /* sigh.  Free what we've alloced and return.  */
	    {
	      new_req->next = _ports_free_rpc_notifies;
	      _ports_free_rpc_notifies = new_req;
	      pthread_mutex_unlock (&_ports_lock);
	      return ENOMEM;
	    }
	}

      pn->reqs = 0;
      pn->port = port;
      pn->what = what;
      pn->pending = 0;
      pthread_mutex_init (&pn->lock, NULL);

      pn->next = _ports_notifications;
      pn->prevp = &_ports_notifications;
      if (_ports_notifications)
	_ports_notifications->prevp = &pn->next;
      _ports_notifications = pn;
    }

  for (req = rpc->notifies; req; req = req->next)
    if (req->notify == pn)
      break;

  if (req)
    /* REQ is already pending for PORT/WHAT on RPC, so free NEW_REQ.  */
    {
      new_req->next = _ports_free_rpc_notifies;
      _ports_free_rpc_notifies = new_req;
    }
  else
    /* Add a new request for PORT/WHAT on RPC.  */
    {
      req = new_req;

      req->rpc = rpc;
      req->notify = pn;
      req->pending = 0;

      req->next_req = pn->reqs;
      req->prev_req_p = &pn->reqs;
      if (pn->reqs)
	pn->reqs->prev_req_p = &req->next_req;
      pn->reqs = req;

      req->next = rpc->notifies;
      rpc->notifies = req;
    }

  /* Make sure that this request results in an interrupt.  */
  req->pending++;

  /* Find out whether we should request a new notification (after we release
     _PORTS_LOCK) -- PN may be new, or left over after a previous
     notification (in which case our new request is likely to trigger an
     immediate notification).  */
  req_notify = !pn->pending;
  if (req_notify)
    pthread_mutex_lock (&pn->lock);

  pthread_mutex_unlock (&_ports_lock);

  if (req_notify)
    {
      mach_port_t old;
      error_t err =
	mach_port_request_notification (mach_task_self (), port,
					what, 1, pi->port_right,
					MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);

      if (! err && old != MACH_PORT_NULL)
	mach_port_deallocate (mach_task_self (), old);

      pn->pending = 1;
      pthread_mutex_unlock (&pn->lock);

      return err;
    }
  else
    return 0;
}

/* Arrange for hurd_cancel to be called on the current thread, which should
   be an rpc on OBJECT, if PORT gets notified with the condition WHAT.  */
error_t
ports_interrupt_self_on_notification (void *object,
				      mach_port_t port, mach_msg_id_t what)
{
  struct rpc_info *rpc;
  struct port_info *pi = object;
  thread_t thread = hurd_thread_self ();

  pthread_mutex_lock (&_ports_lock);
  for (rpc = pi->current_rpcs; rpc; rpc = rpc->next)
    if (rpc->thread == thread)
      break;
  pthread_mutex_unlock (&_ports_lock);

  assert (rpc);

  /* We don't have to worry about RPC going away after we dropped the lock
     because we're that thread, and we're still here.  */
  return ports_interrupt_rpc_on_notification (object, rpc, port, what);
}