| /* 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); |
| } |