Fix unreliable closing of the send end

The current implementation of write end signaling is flaky.  More details in the commit message.

Here is an example to demonstrate the issue.

```haskell
import Control.Concurrent
import Control.Proxy
import Control.Proxy.Concurrent
import Control.Concurrent.Async

main = do
  (input, output) <- spawn (Bounded 3)
  t1 <- async $ do
    runProxy $ fromListS [1..4] >-> sendD input
    performGC
  t2 <- async $ do
    runProxy $ recvS output >-> execD (threadDelay 10000) >-> printD
  wait t1
  wait t2
```

Running this program aborts with:
```
test: thread blocked indefinitely in an STM transaction
```

Instead of just cleanly closing the pipes.