GUACAMOLE-180: Make common surface threadsafe.

This commit is contained in:
Michael Jumper 2016-09-23 13:47:09 -07:00
parent b8f4f19c36
commit b7e0e080da
2 changed files with 163 additions and 54 deletions

View File

@ -29,6 +29,8 @@
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <pthread.h>
/** /**
* The maximum number of updates to allow within the bitmap queue. * The maximum number of updates to allow within the bitmap queue.
*/ */
@ -226,6 +228,13 @@ typedef struct guac_common_surface {
*/ */
guac_common_surface_heat_cell* heat_map; guac_common_surface_heat_cell* heat_map;
/**
* Mutex which is locked internally when access to the surface must be
* synchronized. All public functions of guac_common_surface should be
* considered threadsafe.
*/
pthread_mutex_t _lock;
} guac_common_surface; } guac_common_surface;
/** /**
@ -418,16 +427,6 @@ void guac_common_surface_set_parent(guac_common_surface* surface,
*/ */
void guac_common_surface_set_opacity(guac_common_surface* surface, int opacity); void guac_common_surface_set_opacity(guac_common_surface* surface, int opacity);
/**
* Flushes only the properties of the given surface, such as layer location or
* opacity. Image state is not flushed. If the surface represents a buffer or
* the default layer, this function has no effect.
*
* @param surface
* The surface to flush.
*/
void guac_common_surface_flush_properties(guac_common_surface* surface);
/** /**
* Flushes the given surface, including any applicable properties, drawing any * Flushes the given surface, including any applicable properties, drawing any
* pending operations on the remote display. * pending operations on the remote display.
@ -437,16 +436,6 @@ void guac_common_surface_flush_properties(guac_common_surface* surface);
*/ */
void guac_common_surface_flush(guac_common_surface* surface); void guac_common_surface_flush(guac_common_surface* surface);
/**
* Schedules a deferred flush of the given surface. This will not immediately
* flush the surface to the client. Instead, the result of the flush is
* added to a queue which is reinspected and combined (if possible) with other
* deferred flushes during the call to guac_common_surface_flush().
*
* @param surface The surface to flush.
*/
void guac_common_surface_flush_deferred(guac_common_surface* surface);
/** /**
* Duplicates the contents of the current surface to the given socket. Pending * Duplicates the contents of the current surface to the given socket. Pending
* changes are not flushed. * changes are not flushed.

View File

@ -29,6 +29,7 @@
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
@ -117,26 +118,50 @@
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8 #define GUAC_SURFACE_WEBP_BLOCK_SIZE 8
void guac_common_surface_move(guac_common_surface* surface, int x, int y) { void guac_common_surface_move(guac_common_surface* surface, int x, int y) {
pthread_mutex_lock(&surface->_lock);
surface->x = x; surface->x = x;
surface->y = y; surface->y = y;
surface->location_dirty = 1; surface->location_dirty = 1;
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_stack(guac_common_surface* surface, int z) { void guac_common_surface_stack(guac_common_surface* surface, int z) {
pthread_mutex_lock(&surface->_lock);
surface->z = z; surface->z = z;
surface->location_dirty = 1; surface->location_dirty = 1;
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_set_parent(guac_common_surface* surface, void guac_common_surface_set_parent(guac_common_surface* surface,
const guac_layer* parent) { const guac_layer* parent) {
pthread_mutex_lock(&surface->_lock);
surface->parent = parent; surface->parent = parent;
surface->location_dirty = 1; surface->location_dirty = 1;
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_set_opacity(guac_common_surface* surface, void guac_common_surface_set_opacity(guac_common_surface* surface,
int opacity) { int opacity) {
pthread_mutex_lock(&surface->_lock);
surface->opacity = opacity; surface->opacity = opacity;
surface->opacity_dirty = 1; surface->opacity_dirty = 1;
pthread_mutex_unlock(&surface->_lock);
} }
/** /**
@ -586,16 +611,34 @@ static void __guac_common_surface_flush_to_queue(guac_common_surface* surface) {
} }
void guac_common_surface_flush_deferred(guac_common_surface* surface) { /**
* Flushes the given surface, drawing any pending operations on the remote
* display. Surface properties are not flushed.
*
* @param surface
* The surface to flush.
*/
static void __guac_common_surface_flush(guac_common_surface* surface);
/**
* Schedules a deferred flush of the given surface. This will not immediately
* flush the surface to the client. Instead, the result of the flush is
* added to a queue which is reinspected and combined (if possible) with other
* deferred flushes during the call to guac_common_surface_flush().
*
* @param surface The surface to flush.
*/
static void __guac_common_surface_flush_deferred(guac_common_surface* surface) {
/* Do not flush if not dirty */ /* Do not flush if not dirty */
if (!surface->dirty) if (!surface->dirty)
return; return;
/* Flush if queue size has reached maximum (space is reserved for the final dirty rect, /* Flush if queue size has reached maximum (space is reserved for the final
* as guac_common_surface_flush() MAY add an additional rect to the queue */ * dirty rect, as __guac_common_surface_flush() MAY add an additional rect
* to the queue */
if (surface->bitmap_queue_length == GUAC_COMMON_SURFACE_QUEUE_SIZE-1) if (surface->bitmap_queue_length == GUAC_COMMON_SURFACE_QUEUE_SIZE-1)
guac_common_surface_flush(surface); __guac_common_surface_flush(surface);
/* Append dirty rect to queue */ /* Append dirty rect to queue */
__guac_common_surface_flush_to_queue(surface); __guac_common_surface_flush_to_queue(surface);
@ -1017,6 +1060,8 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client,
surface->width = w; surface->width = w;
surface->height = h; surface->height = h;
pthread_mutex_init(&surface->_lock, NULL);
/* Create corresponding Cairo surface */ /* Create corresponding Cairo surface */
surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
surface->buffer = calloc(h, surface->stride); surface->buffer = calloc(h, surface->stride);
@ -1047,6 +1092,8 @@ void guac_common_surface_free(guac_common_surface* surface) {
if (surface->realized) if (surface->realized)
guac_protocol_send_dispose(surface->socket, surface->layer); guac_protocol_send_dispose(surface->socket, surface->layer);
pthread_mutex_destroy(&surface->_lock);
free(surface->heat_map); free(surface->heat_map);
free(surface->buffer); free(surface->buffer);
free(surface); free(surface);
@ -1055,6 +1102,8 @@ void guac_common_surface_free(guac_common_surface* surface) {
void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { void guac_common_surface_resize(guac_common_surface* surface, int w, int h) {
pthread_mutex_lock(&surface->_lock);
guac_socket* socket = surface->socket; guac_socket* socket = surface->socket;
const guac_layer* layer = surface->layer; const guac_layer* layer = surface->layer;
@ -1104,10 +1153,14 @@ void guac_common_surface_resize(guac_common_surface* surface, int w, int h) {
if (surface->realized) if (surface->realized)
guac_protocol_send_size(socket, layer, w, h); guac_protocol_send_size(socket, layer, w, h);
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_surface_t* src) { void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_surface_t* src) {
pthread_mutex_lock(&surface->_lock);
unsigned char* buffer = cairo_image_surface_get_data(src); unsigned char* buffer = cairo_image_surface_get_data(src);
cairo_format_t format = cairo_image_surface_get_format(src); cairo_format_t format = cairo_image_surface_get_format(src);
int stride = cairo_image_surface_get_stride(src); int stride = cairo_image_surface_get_stride(src);
@ -1123,12 +1176,12 @@ void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_
/* Clip operation */ /* Clip operation */
__guac_common_clip_rect(surface, &rect, &sx, &sy); __guac_common_clip_rect(surface, &rect, &sx, &sy);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update backing surface */ /* Update backing surface */
__guac_common_surface_put(buffer, stride, &sx, &sy, surface, &rect, format != CAIRO_FORMAT_ARGB32); __guac_common_surface_put(buffer, stride, &sx, &sy, surface, &rect, format != CAIRO_FORMAT_ARGB32);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update the heat map for the update rectangle. */ /* Update the heat map for the update rectangle. */
guac_timestamp time = guac_timestamp_current(); guac_timestamp time = guac_timestamp_current();
@ -1136,15 +1189,20 @@ void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_
/* Flush if not combining */ /* Flush if not combining */
if (!__guac_common_should_combine(surface, &rect, 0)) if (!__guac_common_should_combine(surface, &rect, 0))
guac_common_surface_flush_deferred(surface); __guac_common_surface_flush_deferred(surface);
/* Always defer draws */ /* Always defer draws */
__guac_common_mark_dirty(surface, &rect); __guac_common_mark_dirty(surface, &rect);
complete:
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_paint(guac_common_surface* surface, int x, int y, cairo_surface_t* src, void guac_common_surface_paint(guac_common_surface* surface, int x, int y,
int red, int green, int blue) { cairo_surface_t* src, int red, int green, int blue) {
pthread_mutex_lock(&surface->_lock);
unsigned char* buffer = cairo_image_surface_get_data(src); unsigned char* buffer = cairo_image_surface_get_data(src);
int stride = cairo_image_surface_get_stride(src); int stride = cairo_image_surface_get_stride(src);
@ -1160,22 +1218,30 @@ void guac_common_surface_paint(guac_common_surface* surface, int x, int y, cairo
/* Clip operation */ /* Clip operation */
__guac_common_clip_rect(surface, &rect, &sx, &sy); __guac_common_clip_rect(surface, &rect, &sx, &sy);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update backing surface */ /* Update backing surface */
__guac_common_surface_fill_mask(buffer, stride, sx, sy, surface, &rect, red, green, blue); __guac_common_surface_fill_mask(buffer, stride, sx, sy, surface, &rect, red, green, blue);
/* Flush if not combining */ /* Flush if not combining */
if (!__guac_common_should_combine(surface, &rect, 0)) if (!__guac_common_should_combine(surface, &rect, 0))
guac_common_surface_flush_deferred(surface); __guac_common_surface_flush_deferred(surface);
/* Always defer draws */ /* Always defer draws */
__guac_common_mark_dirty(surface, &rect); __guac_common_mark_dirty(surface, &rect);
complete:
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, int w, int h, void guac_common_surface_copy(guac_common_surface* src, int sx, int sy,
guac_common_surface* dst, int dx, int dy) { int w, int h, guac_common_surface* dst, int dx, int dy) {
/* Lock both surfaces */
pthread_mutex_lock(&dst->_lock);
if (src != dst)
pthread_mutex_lock(&src->_lock);
guac_socket* socket = dst->socket; guac_socket* socket = dst->socket;
const guac_layer* src_layer = src->layer; const guac_layer* src_layer = src->layer;
@ -1187,13 +1253,13 @@ void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, int w, i
/* Clip operation */ /* Clip operation */
__guac_common_clip_rect(dst, &rect, &sx, &sy); __guac_common_clip_rect(dst, &rect, &sx, &sy);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update backing surface first only if destination rect cannot intersect source rect */ /* Update backing surface first only if destination rect cannot intersect source rect */
if (src != dst) { if (src != dst) {
__guac_common_surface_transfer(src, &sx, &sy, GUAC_TRANSFER_BINARY_SRC, dst, &rect); __guac_common_surface_transfer(src, &sx, &sy, GUAC_TRANSFER_BINARY_SRC, dst, &rect);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
} }
/* Defer if combining */ /* Defer if combining */
@ -1202,8 +1268,8 @@ void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, int w, i
/* Otherwise, flush and draw immediately */ /* Otherwise, flush and draw immediately */
else { else {
guac_common_surface_flush(dst); __guac_common_surface_flush(dst);
guac_common_surface_flush(src); __guac_common_surface_flush(src);
guac_protocol_send_copy(socket, src_layer, sx, sy, rect.width, rect.height, guac_protocol_send_copy(socket, src_layer, sx, sy, rect.width, rect.height,
GUAC_COMP_OVER, dst_layer, rect.x, rect.y); GUAC_COMP_OVER, dst_layer, rect.x, rect.y);
dst->realized = 1; dst->realized = 1;
@ -1213,11 +1279,23 @@ void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, int w, i
if (src == dst) if (src == dst)
__guac_common_surface_transfer(src, &sx, &sy, GUAC_TRANSFER_BINARY_SRC, dst, &rect); __guac_common_surface_transfer(src, &sx, &sy, GUAC_TRANSFER_BINARY_SRC, dst, &rect);
complete:
/* Unlock both surfaces */
pthread_mutex_unlock(&dst->_lock);
if (src != dst)
pthread_mutex_unlock(&src->_lock);
} }
void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int w, int h, void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int w, int h,
guac_transfer_function op, guac_common_surface* dst, int dx, int dy) { guac_transfer_function op, guac_common_surface* dst, int dx, int dy) {
/* Lock both surfaces */
pthread_mutex_lock(&dst->_lock);
if (src != dst)
pthread_mutex_lock(&src->_lock);
guac_socket* socket = dst->socket; guac_socket* socket = dst->socket;
const guac_layer* src_layer = src->layer; const guac_layer* src_layer = src->layer;
const guac_layer* dst_layer = dst->layer; const guac_layer* dst_layer = dst->layer;
@ -1228,13 +1306,13 @@ void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int
/* Clip operation */ /* Clip operation */
__guac_common_clip_rect(dst, &rect, &sx, &sy); __guac_common_clip_rect(dst, &rect, &sx, &sy);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update backing surface first only if destination rect cannot intersect source rect */ /* Update backing surface first only if destination rect cannot intersect source rect */
if (src != dst) { if (src != dst) {
__guac_common_surface_transfer(src, &sx, &sy, op, dst, &rect); __guac_common_surface_transfer(src, &sx, &sy, op, dst, &rect);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
} }
/* Defer if combining */ /* Defer if combining */
@ -1243,9 +1321,10 @@ void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int
/* Otherwise, flush and draw immediately */ /* Otherwise, flush and draw immediately */
else { else {
guac_common_surface_flush(dst); __guac_common_surface_flush(dst);
guac_common_surface_flush(src); __guac_common_surface_flush(src);
guac_protocol_send_transfer(socket, src_layer, sx, sy, rect.width, rect.height, op, dst_layer, rect.x, rect.y); guac_protocol_send_transfer(socket, src_layer, sx, sy, rect.width,
rect.height, op, dst_layer, rect.x, rect.y);
dst->realized = 1; dst->realized = 1;
} }
@ -1253,11 +1332,19 @@ void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int
if (src == dst) if (src == dst)
__guac_common_surface_transfer(src, &sx, &sy, op, dst, &rect); __guac_common_surface_transfer(src, &sx, &sy, op, dst, &rect);
complete:
/* Unlock both surfaces */
pthread_mutex_unlock(&dst->_lock);
if (src != dst)
pthread_mutex_unlock(&src->_lock);
} }
void guac_common_surface_rect(guac_common_surface* surface, void guac_common_surface_rect(guac_common_surface* surface,
int x, int y, int w, int h, int x, int y, int w, int h, int red, int green, int blue) {
int red, int green, int blue) {
pthread_mutex_lock(&surface->_lock);
guac_socket* socket = surface->socket; guac_socket* socket = surface->socket;
const guac_layer* layer = surface->layer; const guac_layer* layer = surface->layer;
@ -1268,12 +1355,12 @@ void guac_common_surface_rect(guac_common_surface* surface,
/* Clip operation */ /* Clip operation */
__guac_common_clip_rect(surface, &rect, NULL, NULL); __guac_common_clip_rect(surface, &rect, NULL, NULL);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Update backing surface */ /* Update backing surface */
__guac_common_surface_rect(surface, &rect, red, green, blue); __guac_common_surface_rect(surface, &rect, red, green, blue);
if (rect.width <= 0 || rect.height <= 0) if (rect.width <= 0 || rect.height <= 0)
return; goto complete;
/* Defer if combining */ /* Defer if combining */
if (__guac_common_should_combine(surface, &rect, 1)) if (__guac_common_should_combine(surface, &rect, 1))
@ -1281,16 +1368,21 @@ void guac_common_surface_rect(guac_common_surface* surface,
/* Otherwise, flush and draw immediately */ /* Otherwise, flush and draw immediately */
else { else {
guac_common_surface_flush(surface); __guac_common_surface_flush(surface);
guac_protocol_send_rect(socket, layer, rect.x, rect.y, rect.width, rect.height); guac_protocol_send_rect(socket, layer, rect.x, rect.y, rect.width, rect.height);
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, layer, red, green, blue, 0xFF); guac_protocol_send_cfill(socket, GUAC_COMP_OVER, layer, red, green, blue, 0xFF);
surface->realized = 1; surface->realized = 1;
} }
complete:
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w, int h) { void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w, int h) {
pthread_mutex_lock(&surface->_lock);
guac_common_rect clip; guac_common_rect clip;
/* Init clipping rectangle if clipping not already applied */ /* Init clipping rectangle if clipping not already applied */
@ -1302,10 +1394,14 @@ void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w,
guac_common_rect_init(&clip, x, y, w, h); guac_common_rect_init(&clip, x, y, w, h);
guac_common_rect_constrain(&surface->clip_rect, &clip); guac_common_rect_constrain(&surface->clip_rect, &clip);
pthread_mutex_unlock(&surface->_lock);
} }
void guac_common_surface_reset_clip(guac_common_surface* surface) { void guac_common_surface_reset_clip(guac_common_surface* surface) {
pthread_mutex_lock(&surface->_lock);
surface->clipped = 0; surface->clipped = 0;
pthread_mutex_unlock(&surface->_lock);
} }
/** /**
@ -1443,7 +1539,6 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface) {
} }
/** /**
* Comparator for instances of guac_common_surface_bitmap_rect, the elements * Comparator for instances of guac_common_surface_bitmap_rect, the elements
* which make up a surface's bitmap buffer. * which make up a surface's bitmap buffer.
@ -1467,7 +1562,16 @@ static int __guac_common_surface_bitmap_rect_compare(const void* a, const void*
} }
void guac_common_surface_flush_properties(guac_common_surface* surface) { /**
* Flushes only the properties of the given surface, such as layer location or
* opacity. Image state is not flushed. If the surface represents a buffer or
* the default layer, this function has no effect.
*
* @param surface
* The surface to flush.
*/
static void __guac_common_surface_flush_properties(
guac_common_surface* surface) {
guac_socket* socket = surface->socket; guac_socket* socket = surface->socket;
@ -1490,7 +1594,7 @@ void guac_common_surface_flush_properties(guac_common_surface* surface) {
} }
void guac_common_surface_flush(guac_common_surface* surface) { static void __guac_common_surface_flush(guac_common_surface* surface) {
/* Flush final dirty rectangle to queue. */ /* Flush final dirty rectangle to queue. */
__guac_common_surface_flush_to_queue(surface); __guac_common_surface_flush_to_queue(surface);
@ -1570,20 +1674,33 @@ void guac_common_surface_flush(guac_common_surface* surface) {
} }
/* Flush any applicable layer properties */
guac_common_surface_flush_properties(surface);
/* Flush complete */ /* Flush complete */
surface->bitmap_queue_length = 0; surface->bitmap_queue_length = 0;
} }
void guac_common_surface_flush(guac_common_surface* surface) {
pthread_mutex_lock(&surface->_lock);
/* Flush any applicable layer properties */
__guac_common_surface_flush_properties(surface);
/* Flush surface contents */
__guac_common_surface_flush(surface);
pthread_mutex_unlock(&surface->_lock);
}
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
guac_socket* socket) { guac_socket* socket) {
pthread_mutex_lock(&surface->_lock);
/* Do nothing if not realized */ /* Do nothing if not realized */
if (!surface->realized) if (!surface->realized)
return; goto complete;
/* Synchronize layer-specific properties if applicable */ /* Synchronize layer-specific properties if applicable */
if (surface->layer->index > 0) { if (surface->layer->index > 0) {
@ -1611,5 +1728,8 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
0, 0, rect); 0, 0, rect);
cairo_surface_destroy(rect); cairo_surface_destroy(rect);
complete:
pthread_mutex_unlock(&surface->_lock);
} }