aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/046-commit-replica/shipper.c
blob: 57fc877fbcd818463cdfbad3156af1382a372596 (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
/* Research 046: the owner's side of the link. Takes the shim's cuts on a unix socket (u64le the cut's number, u64le
 * its size, its ops), sends each over pure link as records of whole ops (ops.h: u8 1 on its last, the cut's number,
 * the ops; at most RECORD bytes but for an op larger alone; SHIPPER_RECORD sets it), and answers the shim with the cut's number once the
 * link says the follower saved its last record (pure_link_delivered): the owner's sync waits for that.
 *
 *   shipper SOCKET pure://127.0.0.1:PORT/store KEY FOLLOWER_PUBLIC_HEX     at its end, one JSON line of counts */
#define _GNU_SOURCE
#include "link.h"
#include "seal.h"
#include "ops.h"
#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>

static size_t RECORD = 1 << 20; /* SHIPPER_RECORD: smaller, so a cut is several records (one torn at a kill) */
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t saved = PTHREAD_COND_INITIALIZER;

static void notify(void *context) {
    (void)context;
    pthread_mutex_lock(&lock);
    pthread_cond_broadcast(&saved);
    pthread_mutex_unlock(&lock);
}

static bool whole_read(int fd, void *bytes, size_t size) {
    for (unsigned char *p = bytes; size;) {
        ssize_t n = read(fd, p, size);
        if (n <= 0) { if (n < 0 && errno == EINTR) continue; return false; }
        p += n, size -= (size_t)n;
    }
    return true;
}

int main(int argc, char **argv) {
    if (argc != 5) { fprintf(stderr, "usage: %s SOCKET URL KEY FOLLOWER_PUBLIC_HEX\n", argv[0]); return 2; }
    if (getenv("SHIPPER_RECORD")) RECORD = (size_t)atol(getenv("SHIPPER_RECORD"));
    char error[256], public_hex[2 * PURE_SEAL_KEY + 1];
    unsigned char own_public[PURE_SEAL_KEY], peer[PURE_SEAL_KEY];
    PureSealKey *key = pure_seal_key(argv[3], public_hex, error, sizeof(error));
    if (!key || !pure_seal_peer(public_hex, own_public) || !pure_seal_peer(argv[4], peer)) { fprintf(stderr, "shipper: %s\n", error); return 1; }
    PureLink *link = pure_link_open(argv[2], "owner", 1, key, own_public, error, sizeof(error));
    if (!link) { fprintf(stderr, "shipper: %s\n", error); return 1; }
    pure_link_peer(link, peer);
    if (!pure_link_start(link, notify, NULL, error, sizeof(error))) { fprintf(stderr, "shipper: %s\n", error); return 1; }
    struct sockaddr_un address = {.sun_family = AF_UNIX};
    if (strlen(argv[1]) >= sizeof(address.sun_path)) return 1;
    strcpy(address.sun_path, argv[1]);
    unlink(argv[1]);
    int listening = socket(AF_UNIX, SOCK_STREAM, 0);
    if (listening < 0 || bind(listening, (struct sockaddr *)&address, sizeof(address)) || listen(listening, 1)) {
        perror("shipper: socket");
        return 1;
    }
    printf("{\"ready\":true}\n");
    fflush(stdout);
    int shim = accept(listening, NULL, NULL);
    if (shim < 0) return 1;
    uint64_t seq = 0, cuts = 0, bytes = 0, records = 0, waited_ns = 0;
    unsigned char *cut = NULL, *record = malloc(RECORD + 64);
    size_t room = 0;
    for (;;) {
        unsigned char head[16];
        if (!whole_read(shim, head, sizeof(head))) break; /* the owner ended (or was killed) */
        uint64_t number = get64(head), size = get64(head + 8);
        if (size > room && !(cut = realloc(cut, room = size))) return 1;
        if (!whole_read(shim, cut, size)) break;
        /* whole ops into records */
        for (size_t at = 0; at < size;) {
            size_t length = 9;
            size_t end = at;
            while (end < size) {
                size_t n = op_size(cut + end, size - end);
                if (!n) { fprintf(stderr, "shipper: a torn op in cut %llu\n", (unsigned long long)number); return 1; }
                if (end > at && length + n > RECORD) break;
                length += n, end += n;
            }
            unsigned char *r = length <= RECORD + 64 ? record : malloc(length);
            r[0] = end == size;
            put64(r + 1, number);
            memcpy(r + 9, cut + at, end - at);
            if (!pure_link_queue(link, ++seq, r, length)) { fprintf(stderr, "shipper: not queued\n"); return 1; }
            if (r != record) free(r);
            ++records, bytes += length;
            at = end;
        }
        struct timespec a, b;
        clock_gettime(CLOCK_MONOTONIC, &a);
        pure_link_push(link);
        pthread_mutex_lock(&lock);
        while (pure_link_delivered(link) < seq) {
            struct timespec until;
            clock_gettime(CLOCK_REALTIME, &until);
            until.tv_nsec += 10 * 1000000;
            if (until.tv_nsec >= 1000000000) until.tv_sec += 1, until.tv_nsec -= 1000000000;
            pthread_cond_timedwait(&saved, &lock, &until);
        }
        pthread_mutex_unlock(&lock);
        clock_gettime(CLOCK_MONOTONIC, &b);
        waited_ns += (uint64_t)((b.tv_sec - a.tv_sec) * 1000000000LL + (b.tv_nsec - a.tv_nsec));
        ++cuts;
        unsigned char ack[8];
        put64(ack, number);
        if (write(shim, ack, sizeof(ack)) != sizeof(ack)) break;
    }
    PureLinkCounts counts;
    pure_link_counts(link, &counts);
    printf("{\"cuts\":%llu,\"records\":%llu,\"record_bytes\":%llu,\"waited_ms\":%.1f,\"packets\":%llu,\"resent\":%llu}\n",
           (unsigned long long)cuts, (unsigned long long)records, (unsigned long long)bytes, waited_ns / 1e6,
           (unsigned long long)counts.packets, (unsigned long long)counts.resent);
    pure_link_close(link);
    pure_seal_key_free(key);
    return 0;
}