| |
| #include "rust_port.h" |
| #include "rust_task.h" |
| |
| rust_port::rust_port(rust_task *task, size_t unit_sz) |
| : ref_count(1), kernel(task->kernel), task(task), |
| unit_sz(unit_sz), buffer(kernel, unit_sz) { |
| |
| LOG(task, comm, |
| "new rust_port(task=0x%" PRIxPTR ", unit_sz=%d) -> port=0x%" |
| PRIxPTR, (uintptr_t)task, unit_sz, (uintptr_t)this); |
| |
| id = kernel->register_port(this); |
| } |
| |
| rust_port::~rust_port() { |
| LOG(task, comm, "~rust_port 0x%" PRIxPTR, (uintptr_t) this); |
| } |
| |
| void rust_port::ref() { |
| scoped_lock with(ref_lock); |
| ref_count++; |
| } |
| |
| void rust_port::deref() { |
| scoped_lock with(ref_lock); |
| ref_count--; |
| if (!ref_count) { |
| // The port owner is waiting for the port to be detached (if it |
| // hasn't already been killed) |
| scoped_lock with(task->lifecycle_lock); |
| if (task->blocked_on(&detach_cond)) { |
| task->wakeup_inner(&detach_cond); |
| } |
| } |
| } |
| |
| void rust_port::begin_detach(uintptr_t *yield) { |
| *yield = false; |
| |
| kernel->release_port_id(id); |
| |
| scoped_lock with(ref_lock); |
| ref_count--; |
| |
| if (ref_count != 0) { |
| task->block(&detach_cond, "waiting for port detach"); |
| *yield = true; |
| } |
| } |
| |
| void rust_port::end_detach() { |
| // Just take the lock to make sure that the thread that signaled |
| // the detach_cond isn't still holding it |
| scoped_lock with(ref_lock); |
| assert(ref_count == 0); |
| } |
| |
| void rust_port::send(void *sptr) { |
| bool did_rendezvous = false; |
| { |
| scoped_lock with(lock); |
| |
| buffer.enqueue(sptr); |
| |
| assert(!buffer.is_empty() && |
| "rust_chan::transmit with nothing to send."); |
| |
| { |
| scoped_lock with(task->lifecycle_lock); |
| if (task->blocked_on(this)) { |
| KLOG(kernel, comm, "dequeued in rendezvous_ptr"); |
| buffer.dequeue(task->rendezvous_ptr); |
| task->rendezvous_ptr = 0; |
| task->wakeup_inner(this); |
| did_rendezvous = true; |
| } |
| } |
| } |
| |
| if (!did_rendezvous) { |
| // If the task wasn't waiting specifically on this port, |
| // it may be waiting on a group of ports |
| |
| rust_port_selector *port_selector = task->get_port_selector(); |
| // The port selector will check if the task is blocked, not us. |
| port_selector->msg_sent_on(this); |
| } |
| } |
| |
| void rust_port::receive(void *dptr, uintptr_t *yield) { |
| LOG(task, comm, "port: 0x%" PRIxPTR ", dptr: 0x%" PRIxPTR |
| ", size: 0x%" PRIxPTR, |
| (uintptr_t) this, (uintptr_t) dptr, unit_sz); |
| |
| scoped_lock with(lock); |
| |
| *yield = false; |
| |
| if (buffer.is_empty() == false) { |
| buffer.dequeue(dptr); |
| LOG(task, comm, "<=== read data ==="); |
| return; |
| } |
| memset(dptr, 0, buffer.unit_sz); |
| |
| // No data was buffered on any incoming channel, so block this task on |
| // the port. Remember the rendezvous location so that any sender task |
| // can write to it before waking up this task. |
| |
| LOG(task, comm, "<=== waiting for rendezvous data ==="); |
| task->rendezvous_ptr = (uintptr_t*) dptr; |
| task->block(this, "waiting for rendezvous data"); |
| |
| // Blocking the task might fail if the task has already been killed, but |
| // in the event of both failure and success the task needs to yield. On |
| // success, it yields and waits to be unblocked. On failure it yields and |
| // is then fails the task. |
| |
| *yield = true; |
| } |
| |
| size_t rust_port::size() { |
| scoped_lock with(lock); |
| return buffer.size(); |
| } |
| |
| void rust_port::log_state() { |
| LOG(task, comm, |
| "port size: %d", |
| buffer.size()); |
| } |
| |
| // |
| // Local Variables: |
| // mode: C++ |
| // fill-column: 78; |
| // indent-tabs-mode: nil |
| // c-basic-offset: 4 |
| // buffer-file-coding-system: utf-8-unix |
| // compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'"; |
| // End: |
| // |