mirror of
https://github.com/wassname/ray.git
synced 2026-07-14 11:17:54 +08:00
413 lines
14 KiB
C
413 lines
14 KiB
C
/* PLASMA STORE: This is a simple object store server process
|
|
*
|
|
* It accepts incoming client connections on a unix domain socket
|
|
* (name passed in via the -s option of the executable) and uses a
|
|
* single thread to serve the clients. Each client establishes a
|
|
* connection and can create objects, wait for objects and seal
|
|
* objects through that connection.
|
|
*
|
|
* It keeps a hash table that maps object_ids (which are 20 byte long,
|
|
* just enough to store and SHA1 hash) to memory mapped files. */
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <getopt.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
|
|
#include "common.h"
|
|
#include "event_loop.h"
|
|
#include "io.h"
|
|
#include "uthash.h"
|
|
#include "utarray.h"
|
|
#include "fling.h"
|
|
#include "malloc.h"
|
|
#include "plasma_store.h"
|
|
|
|
void *dlmalloc(size_t);
|
|
void dlfree(void *);
|
|
|
|
/**
|
|
* This is used by the Plasma Store to send a reply to the Plasma Client.
|
|
*/
|
|
void plasma_send_reply(int fd, plasma_reply *reply) {
|
|
int reply_count = sizeof(plasma_reply);
|
|
if (write(fd, reply, reply_count) != reply_count) {
|
|
LOG_ERR("write error, fd = %d", fd);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
/* Object id of this object. */
|
|
object_id object_id;
|
|
/* Object info like size, creation time and owner. */
|
|
plasma_object_info info;
|
|
/* Memory mapped file containing the object. */
|
|
int fd;
|
|
/* Size of the underlying map. */
|
|
int64_t map_size;
|
|
/* Offset from the base of the mmap. */
|
|
ptrdiff_t offset;
|
|
/* Handle for the uthash table. */
|
|
UT_hash_handle handle;
|
|
/* Pointer to the object data. Needed to free the object. */
|
|
uint8_t *pointer;
|
|
} object_table_entry;
|
|
|
|
typedef struct {
|
|
/* Object id of this object. */
|
|
object_id object_id;
|
|
/* Socket connections of waiting clients. */
|
|
UT_array *conns;
|
|
/* Handle for the uthash table. */
|
|
UT_hash_handle handle;
|
|
} object_notify_entry;
|
|
|
|
/* This is used to define the array of object IDs used to define the
|
|
* notification_queue type. */
|
|
UT_icd object_id_icd = {sizeof(object_id), NULL, NULL, NULL};
|
|
|
|
typedef struct {
|
|
/** Client file descriptor. This is used as a key for the hash table. */
|
|
int subscriber_fd;
|
|
/** The object IDs to notify the client about. We notify the client about the
|
|
* IDs in the order that the objects were sealed. */
|
|
UT_array *object_ids;
|
|
/** Handle for the uthash table. */
|
|
UT_hash_handle hh;
|
|
} notification_queue;
|
|
|
|
struct plasma_store_state {
|
|
/* Event loop of the plasma store. */
|
|
event_loop *loop;
|
|
/* Objects that are still being written by their owner process. */
|
|
object_table_entry *open_objects;
|
|
/* Objects that have already been sealed by their owner process and
|
|
* can now be shared with other processes. */
|
|
object_table_entry *sealed_objects;
|
|
/* Objects that processes are waiting for. */
|
|
object_notify_entry *objects_notify;
|
|
/** The pending notifications that have not been sent to subscribers because
|
|
* the socket send buffers were full. This is a hash table from client file
|
|
* descriptor to an array of object_ids to send to that client. */
|
|
notification_queue *pending_notifications;
|
|
};
|
|
|
|
plasma_store_state *init_plasma_store(event_loop *loop) {
|
|
plasma_store_state *state = malloc(sizeof(plasma_store_state));
|
|
state->loop = loop;
|
|
state->open_objects = NULL;
|
|
state->sealed_objects = NULL;
|
|
state->objects_notify = NULL;
|
|
state->pending_notifications = NULL;
|
|
return state;
|
|
}
|
|
|
|
/* Create a new object buffer in the hash table. */
|
|
void create_object(plasma_store_state *s,
|
|
object_id object_id,
|
|
int64_t data_size,
|
|
int64_t metadata_size,
|
|
plasma_object *result) {
|
|
LOG_DEBUG("creating object"); /* TODO(pcm): add object_id here */
|
|
|
|
object_table_entry *entry;
|
|
HASH_FIND(handle, s->open_objects, &object_id, sizeof(object_id), entry);
|
|
CHECKM(entry == NULL, "Cannot create object twice.");
|
|
|
|
uint8_t *pointer = dlmalloc(data_size + metadata_size);
|
|
int fd;
|
|
int64_t map_size;
|
|
ptrdiff_t offset;
|
|
get_malloc_mapinfo(pointer, &fd, &map_size, &offset);
|
|
assert(fd != -1);
|
|
|
|
entry = malloc(sizeof(object_table_entry));
|
|
memcpy(&entry->object_id, &object_id, 20);
|
|
entry->info.data_size = data_size;
|
|
entry->info.metadata_size = metadata_size;
|
|
entry->pointer = pointer;
|
|
/* TODO(pcm): set the other fields */
|
|
entry->fd = fd;
|
|
entry->map_size = map_size;
|
|
entry->offset = offset;
|
|
HASH_ADD(handle, s->open_objects, object_id, sizeof(object_id), entry);
|
|
result->handle.store_fd = fd;
|
|
result->handle.mmap_size = map_size;
|
|
result->data_offset = offset;
|
|
result->metadata_offset = offset + data_size;
|
|
result->data_size = data_size;
|
|
result->metadata_size = metadata_size;
|
|
}
|
|
|
|
/* Get an object from the hash table. */
|
|
int get_object(plasma_store_state *s,
|
|
int conn,
|
|
object_id object_id,
|
|
plasma_object *result) {
|
|
object_table_entry *entry;
|
|
HASH_FIND(handle, s->sealed_objects, &object_id, sizeof(object_id), entry);
|
|
if (entry) {
|
|
result->handle.store_fd = entry->fd;
|
|
result->handle.mmap_size = entry->map_size;
|
|
result->data_offset = entry->offset;
|
|
result->metadata_offset = entry->offset + entry->info.data_size;
|
|
result->data_size = entry->info.data_size;
|
|
result->metadata_size = entry->info.metadata_size;
|
|
return OBJECT_FOUND;
|
|
} else {
|
|
object_notify_entry *notify_entry;
|
|
LOG_DEBUG("object not in hash table of sealed objects");
|
|
HASH_FIND(handle, s->objects_notify, &object_id, sizeof(object_id),
|
|
notify_entry);
|
|
if (!notify_entry) {
|
|
notify_entry = malloc(sizeof(object_notify_entry));
|
|
memset(notify_entry, 0, sizeof(object_notify_entry));
|
|
utarray_new(notify_entry->conns, &ut_int_icd);
|
|
memcpy(¬ify_entry->object_id, &object_id, 20);
|
|
HASH_ADD(handle, s->objects_notify, object_id, sizeof(object_id),
|
|
notify_entry);
|
|
}
|
|
utarray_push_back(notify_entry->conns, &conn);
|
|
}
|
|
return OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
/* Check if an object is present. */
|
|
int contains_object(plasma_store_state *s, object_id object_id) {
|
|
object_table_entry *entry;
|
|
HASH_FIND(handle, s->sealed_objects, &object_id, sizeof(object_id), entry);
|
|
return entry ? OBJECT_FOUND : OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
/* Seal an object that has been created in the hash table. */
|
|
void seal_object(plasma_store_state *s,
|
|
object_id object_id,
|
|
UT_array **conns,
|
|
plasma_object *result) {
|
|
LOG_DEBUG("sealing object"); // TODO(pcm): add object_id here
|
|
object_table_entry *entry;
|
|
HASH_FIND(handle, s->open_objects, &object_id, sizeof(object_id), entry);
|
|
if (!entry) {
|
|
return; /* TODO(pcm): return error */
|
|
}
|
|
HASH_DELETE(handle, s->open_objects, entry);
|
|
HASH_ADD(handle, s->sealed_objects, object_id, sizeof(object_id), entry);
|
|
|
|
/* Inform all subscribers that a new object has been sealed. */
|
|
notification_queue *queue, *temp_queue;
|
|
HASH_ITER(hh, s->pending_notifications, queue, temp_queue) {
|
|
utarray_push_back(queue->object_ids, &object_id);
|
|
send_notifications(s->loop, queue->subscriber_fd, s, 0);
|
|
}
|
|
|
|
/* Inform processes getting this object that the object is ready now. */
|
|
object_notify_entry *notify_entry;
|
|
HASH_FIND(handle, s->objects_notify, &object_id, sizeof(object_id),
|
|
notify_entry);
|
|
if (!notify_entry) {
|
|
*conns = NULL;
|
|
return;
|
|
}
|
|
result->handle.store_fd = entry->fd;
|
|
result->handle.mmap_size = entry->map_size;
|
|
result->data_offset = entry->offset;
|
|
result->metadata_offset = entry->offset + entry->info.data_size;
|
|
result->data_size = entry->info.data_size;
|
|
result->metadata_size = entry->info.metadata_size;
|
|
HASH_DELETE(handle, s->objects_notify, notify_entry);
|
|
*conns = notify_entry->conns;
|
|
free(notify_entry);
|
|
}
|
|
|
|
/* Delete an object that has been created in the hash table. */
|
|
void delete_object(plasma_store_state *s, object_id object_id) {
|
|
LOG_DEBUG("deleting object"); // TODO(rkn): add object_id here
|
|
object_table_entry *entry;
|
|
HASH_FIND(handle, s->sealed_objects, &object_id, sizeof(object_id), entry);
|
|
/* TODO(rkn): This should probably not fail, but should instead throw an
|
|
* error. Maybe we should also support deleting objects that have been created
|
|
* but not sealed. */
|
|
CHECKM(entry != NULL, "To delete an object it must have been sealed.");
|
|
uint8_t *pointer = entry->pointer;
|
|
HASH_DELETE(handle, s->sealed_objects, entry);
|
|
dlfree(pointer);
|
|
free(entry);
|
|
}
|
|
|
|
/* Send more notifications to a subscriber. */
|
|
void send_notifications(event_loop *loop,
|
|
int client_sock,
|
|
void *context,
|
|
int events) {
|
|
plasma_store_state *s = context;
|
|
|
|
notification_queue *queue;
|
|
HASH_FIND_INT(s->pending_notifications, &client_sock, queue);
|
|
CHECK(queue != NULL);
|
|
|
|
int num_processed = 0;
|
|
/* Loop over the array of pending notifications and send as many of them as
|
|
* possible. */
|
|
for (object_id *obj_id = (object_id *) utarray_front(queue->object_ids);
|
|
obj_id != NULL;
|
|
obj_id = (object_id *) utarray_next(queue->object_ids, obj_id)) {
|
|
/* Attempt to send a notification about this object ID. */
|
|
int nbytes = send(client_sock, obj_id, sizeof(object_id), 0);
|
|
if (nbytes >= 0) {
|
|
CHECK(nbytes == sizeof(object_id));
|
|
} else if (nbytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
LOG_DEBUG(
|
|
"The socket's send buffer is full, so we are caching this "
|
|
"notification and will send it later.");
|
|
break;
|
|
} else {
|
|
CHECKM(0, "This code should be unreachable.");
|
|
}
|
|
num_processed += 1;
|
|
}
|
|
/* Remove the sent notifications from the array. */
|
|
utarray_erase(queue->object_ids, 0, num_processed);
|
|
}
|
|
|
|
/* Subscribe to notifications about sealed objects. */
|
|
void subscribe_to_updates(plasma_store_state *s, int conn) {
|
|
LOG_DEBUG("subscribing to updates");
|
|
char dummy;
|
|
int fd = recv_fd(conn, &dummy, 1);
|
|
CHECKM(HASH_CNT(handle, s->open_objects) == 0,
|
|
"plasma_subscribe should be called before any objects are created.");
|
|
CHECKM(HASH_CNT(handle, s->sealed_objects) == 0,
|
|
"plasma_subscribe should be called before any objects are created.");
|
|
/* Create a new array to buffer notifications that can't be sent to the
|
|
* subscriber yet because the socket send buffer is full. TODO(rkn): the queue
|
|
* never gets freed. */
|
|
notification_queue *queue =
|
|
(notification_queue *) malloc(sizeof(notification_queue));
|
|
queue->subscriber_fd = fd;
|
|
utarray_new(queue->object_ids, &object_id_icd);
|
|
HASH_ADD_INT(s->pending_notifications, subscriber_fd, queue);
|
|
/* Add a callback to the event loop to send queued notifications whenever
|
|
* there is room in the socket's send buffer. */
|
|
event_loop_add_file(s->loop, fd, EVENT_LOOP_WRITE, send_notifications, s);
|
|
}
|
|
|
|
void process_message(event_loop *loop,
|
|
int client_sock,
|
|
void *context,
|
|
int events) {
|
|
plasma_store_state *s = context;
|
|
int64_t type;
|
|
int64_t length;
|
|
plasma_request *req;
|
|
read_message(client_sock, &type, &length, (uint8_t **) &req);
|
|
plasma_reply reply;
|
|
memset(&reply, 0, sizeof(reply));
|
|
UT_array *conns;
|
|
|
|
switch (type) {
|
|
case PLASMA_CREATE:
|
|
create_object(s, req->object_id, req->data_size, req->metadata_size,
|
|
&reply.object);
|
|
send_fd(client_sock, reply.object.handle.store_fd, (char *) &reply,
|
|
sizeof(reply));
|
|
break;
|
|
case PLASMA_GET:
|
|
if (get_object(s, client_sock, req->object_id, &reply.object) ==
|
|
OBJECT_FOUND) {
|
|
send_fd(client_sock, reply.object.handle.store_fd, (char *) &reply,
|
|
sizeof(reply));
|
|
}
|
|
break;
|
|
case PLASMA_CONTAINS:
|
|
if (contains_object(s, req->object_id) == OBJECT_FOUND) {
|
|
reply.has_object = 1;
|
|
}
|
|
plasma_send_reply(client_sock, &reply);
|
|
break;
|
|
case PLASMA_SEAL:
|
|
seal_object(s, req->object_id, &conns, &reply.object);
|
|
if (conns) {
|
|
for (int *c = (int *) utarray_front(conns); c != NULL;
|
|
c = (int *) utarray_next(conns, c)) {
|
|
send_fd(*c, reply.object.handle.store_fd, (char *) &reply,
|
|
sizeof(reply));
|
|
}
|
|
utarray_free(conns);
|
|
}
|
|
break;
|
|
case PLASMA_DELETE:
|
|
delete_object(s, req->object_id);
|
|
break;
|
|
case PLASMA_SUBSCRIBE:
|
|
subscribe_to_updates(s, client_sock);
|
|
break;
|
|
case DISCONNECT_CLIENT: {
|
|
LOG_DEBUG("Disconnecting client on fd %d", client_sock);
|
|
event_loop_remove_file(loop, client_sock);
|
|
} break;
|
|
default:
|
|
/* This code should be unreachable. */
|
|
CHECK(0);
|
|
}
|
|
|
|
free(req);
|
|
}
|
|
|
|
void new_client_connection(event_loop *loop,
|
|
int listener_sock,
|
|
void *context,
|
|
int events) {
|
|
int new_socket = accept_client(listener_sock);
|
|
event_loop_add_file(loop, new_socket, EVENT_LOOP_READ, process_message,
|
|
context);
|
|
LOG_DEBUG("new connection with fd %d", new_socket);
|
|
}
|
|
|
|
/* Report "success" to valgrind. */
|
|
void signal_handler(int signal) {
|
|
if (signal == SIGTERM) {
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
void start_server(char *socket_name) {
|
|
int socket = bind_ipc_sock(socket_name);
|
|
CHECK(socket >= 0);
|
|
event_loop *loop = event_loop_create();
|
|
plasma_store_state *state = init_plasma_store(loop);
|
|
event_loop_add_file(loop, socket, EVENT_LOOP_READ, new_client_connection,
|
|
state);
|
|
event_loop_run(loop);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
signal(SIGTERM, signal_handler);
|
|
char *socket_name = NULL;
|
|
int c;
|
|
while ((c = getopt(argc, argv, "s:")) != -1) {
|
|
switch (c) {
|
|
case 's':
|
|
socket_name = optarg;
|
|
break;
|
|
default:
|
|
exit(-1);
|
|
}
|
|
}
|
|
if (!socket_name) {
|
|
LOG_ERR("please specify socket for incoming connections with -s switch");
|
|
exit(-1);
|
|
}
|
|
LOG_DEBUG("starting server listening on %s", socket_name);
|
|
start_server(socket_name);
|
|
}
|