summaryrefslogtreecommitdiff
path: root/libgo/runtime/go-send-small.c
blob: 506c90e648d099ca44003c7d15b435cf122a6af3 (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
/* go-send-small.c -- send something 64 bits or smaller on a channel.

   Copyright 2009 The Go Authors. All rights reserved.
   Use of this source code is governed by a BSD-style
   license that can be found in the LICENSE file.  */

#include <stdint.h>

#include "go-assert.h"
#include "go-panic.h"
#include "channel.h"

/* Prepare to send something on a channel.  Return true if the channel
   is acquired, false, if it is closed.  FOR_SELECT is true if this
   call is being made after a select statement returned with this
   channel selected.  */

_Bool
__go_send_acquire (struct __go_channel *channel, _Bool for_select)
{
  int i;

  i = pthread_mutex_lock (&channel->lock);
  __go_assert (i == 0);

  while (1)
    {
      /* Check whether the channel is closed.  */
      if (channel->is_closed)
	{
	  ++channel->closed_op_count;
	  if (channel->closed_op_count >= MAX_CLOSED_OPERATIONS)
	    {
	      i = pthread_mutex_unlock (&channel->lock);
	      __go_assert (i == 0);
	      __go_panic_msg ("too many operations on closed channel");
	    }
	  channel->selected_for_send = 0;
	  __go_unlock_and_notify_selects (channel);
	  return 0;
	}

      /* If somebody else has the channel locked for sending, we have
	 to wait.  If FOR_SELECT is true, then we are the one with the
	 lock.  */
      if (!channel->selected_for_send || for_select)
	{
	  if (channel->num_entries == 0)
	    {
	      /* This is a synchronous channel.  If nobody else is
		 waiting to send, we grab the channel and tell the
		 caller to send the data.  We will then wait for a
		 receiver.  */
	      if (!channel->waiting_to_send)
		{
		  __go_assert (channel->next_store == 0);
		  return 1;
		}
	    }
	  else
	    {
	      /* If there is room on the channel, we are OK.  */
	      if ((channel->next_store + 1) % channel->num_entries
		  != channel->next_fetch)
		return 1;
	    }
	}

      /* Wait for something to change, then loop around and try
	 again.  */

      i = pthread_cond_wait (&channel->cond, &channel->lock);
      __go_assert (i == 0);
    }
}

/* Finished sending something on a channel.  */

void
__go_send_release (struct __go_channel *channel)
{
  int i;

  if (channel->num_entries != 0)
    {
      /* This is a buffered channel.  Bump the store count and signal
	 the condition variable.  */
      channel->next_store = (channel->next_store + 1) % channel->num_entries;

      i = pthread_cond_signal (&channel->cond);
      __go_assert (i == 0);
    }
  else
    {
      _Bool synched_with_select;

      /* This is a synchronous channel.  Indicate that we have a value
	 waiting.  */
      channel->next_store = 1;
      channel->waiting_to_send = 1;

      /* Tell everybody else to do something.  This has to be a
	 broadcast because we might have both senders and receivers
	 waiting on the condition, but senders won't send another
	 signal.  */
      i = pthread_cond_broadcast (&channel->cond);
      __go_assert (i == 0);

      /* Wait until the value is received.  */
      synched_with_select = 0;
      while (1)
	{
	  if (channel->next_store == 0)
	    break;

	  /* If nobody is currently waiting to receive, try to synch
	     up with a select.  */
	  if (!channel->waiting_to_receive && !synched_with_select)
	    {
	      if (__go_synch_with_select (channel, 1))
		{
		  synched_with_select = 1;
		  __go_broadcast_to_select (channel);
		  continue;
		}
	    }

	  i = pthread_cond_wait (&channel->cond, &channel->lock);
	  __go_assert (i == 0);
	}

      channel->waiting_to_send = 0;

      /* Using the mutexes should implement a memory barrier.  */

      /* We have to signal again since we cleared the waiting_to_send
	 field.  This has to be a broadcast because both senders and
	 receivers might be waiting, but only senders will be able to
	 act.  */
      i = pthread_cond_broadcast (&channel->cond);
      __go_assert (i == 0);
    }

  channel->selected_for_send = 0;

  __go_unlock_and_notify_selects (channel);
}

/* Send something 64 bits or smaller on a channel.  */

void
__go_send_small (struct __go_channel *channel, uint64_t val, _Bool for_select)
{
  if (channel == NULL)
    __go_panic_msg ("send to nil channel");

  __go_assert (channel->element_size <= sizeof (uint64_t));

  if (!__go_send_acquire (channel, for_select))
    return;

  channel->data[channel->next_store] = val;

  __go_send_release (channel);
}