Skip to content

Commit

Permalink
move mmap pseudo-network stuff to xpra.net package
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@3173 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Apr 26, 2013
1 parent 4fe8194 commit 2c3f04a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 95 deletions.
2 changes: 1 addition & 1 deletion src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def init_mmap(self, mmap_group, socket_filename):
self.mmap = mmap.mmap(fd, length=self.mmap_size)
#write the 16 byte token one byte at a time - no endianness
self.mmap_token = get_int_uuid()
log.debug("mmap_token=%s", self.mmap_token)
log("mmap_token=%s", self.mmap_token)
v = self.mmap_token
for i in range(0,16):
poke = ctypes.c_ubyte.from_buffer(self.mmap, 512+i)
Expand Down
20 changes: 3 additions & 17 deletions src/xpra/client/window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
gobject = import_gobject()

import os
import ctypes
import cairo
import zlib

Expand All @@ -20,6 +19,7 @@
from threading import Lock
from xpra.scripts.config import ENCODINGS
from xpra.codecs.xor import xor_str
from xpra.net.mmap_pipe import mmap_read

try:
from xpra.codecs.x264.codec import Decoder as x264_Decoder #@UnresolvedImport
Expand Down Expand Up @@ -214,22 +214,8 @@ def paint_mmap(self, img_data, x, y, width, height, rowstride, options, callback
#and would complicate the code (add a callback to free mmap area)
""" see _mmap_send() in server.py for details """
assert self.mmap_enabled
data_start = ctypes.c_uint.from_buffer(self.mmap, 0)
if len(img_data)==1:
#construct an array directly from the mmap zone:
offset, length = img_data[0]
arraytype = ctypes.c_char * length
data = arraytype.from_buffer(self.mmap, offset)
self.do_paint_rgb24(data, x, y, width, height, rowstride, options, callbacks)
data_start.value = offset+length
else:
#re-construct the buffer from discontiguous chunks:
data = ""
for offset, length in img_data:
self.mmap.seek(offset)
data += self.mmap.read(length)
data_start.value = offset+length
self.do_paint_rgb24(data, x, y, width, height, rowstride, options, callbacks)
data = mmap_read(self.mmap, img_data)
self.do_paint_rgb24(data, x, y, width, height, rowstride, options, callbacks)
return False


Expand Down
106 changes: 106 additions & 0 deletions src/xpra/net/mmap_pipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# This file is part of Xpra.
# Copyright (C) 2011-2013 Antoine Martin <antoine@devloop.org.uk>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import ctypes
from wimpiggy.log import Logger
log = Logger()
debug = log.debug
warn = log.warn

#descr_data is a list of (offset, length)
#areas from the mmap region
def mmap_read(mmap_area, descr_data):
"""
Reads data from the mmap_area as written by 'mmap_write'.
The descr_data is the list of mmap chunks used.
"""
data_start = ctypes.c_uint.from_buffer(mmap_area, 0)
if len(descr_data)==1:
#construct an array directly from the mmap zone:
offset, length = descr_data[0]
arraytype = ctypes.c_char * length
data_start.value = offset+length
return arraytype.from_buffer(mmap_area, offset)
#re-construct the buffer from discontiguous chunks:
data = ""
for offset, length in descr_data:
mmap_area.seek(offset)
data += mmap_area.read(length)
data_start.value = offset+length
return data


def mmap_write(mmap_area, mmap_size, data):
"""
Sends 'data' to the client via the mmap shared memory region,
returns the chunks of the mmap area used (or None if it failed)
and the mmap area's free memory.
"""
#This is best explained using diagrams:
#mmap_area=[&S&E-------------data-------------]
#The first pair of 4 bytes are occupied by:
#S=data_start index is only updated by the client and tells us where it has read up to
#E=data_end index is only updated here and marks where we have written up to (matches current seek)
# '-' denotes unused/available space
# '+' is for data we have written
# '*' is for data we have just written in this call
# E and S show the location pointed to by data_start/data_end
mmap_data_start = ctypes.c_uint.from_buffer(mmap_area, 0)
mmap_data_end = ctypes.c_uint.from_buffer(mmap_area, 4)
start = max(8, mmap_data_start.value)
end = max(8, mmap_data_end.value)
if end<start:
#we have wrapped around but the client hasn't yet:
#[++++++++E--------------------S+++++]
#so there is one chunk available (from E to S):
available = start-end
chunk = available
else:
#we have not wrapped around yet, or the client has wrapped around too:
#[------------S++++++++++++E---------]
#so there are two chunks available (from E to the end, from the start to S):
chunk = mmap_size-end
available = chunk+(start-8)
l = len(data)
#update global mmap stats:
mmap_free_size = available-l
if mmap_free_size<=0:
warn("mmap area full: we need more than %s but only %s left! ouch!", l, available)
return None, mmap_free_size
if l<chunk:
""" data fits in the first chunk """
#ie: initially:
#[----------------------------------]
#[*********E------------------------]
#or if data already existed:
#[+++++++++E------------------------]
#[+++++++++**********E--------------]
mmap_area.seek(end)
mmap_area.write(data)
data = [(end, l)]
mmap_data_end.value = end+l
else:
""" data does not fit in first chunk alone """
if available>=(mmap_size/2) and available>=(l*3) and l<(start-8):
""" still plenty of free space, don't wrap around: just start again """
#[------------------S+++++++++E------]
#[*******E----------S+++++++++-------]
mmap_area.seek(8)
mmap_area.write(data)
data = [(8, l)]
mmap_data_end.value = 8+l
else:
""" split in 2 chunks: wrap around the end of the mmap buffer """
#[------------------S+++++++++E------]
#[******E-----------S+++++++++*******]
mmap_area.seek(end)
mmap_area.write(data[:chunk])
mmap_area.seek(8)
mmap_area.write(data[chunk:])
l2 = l-chunk
data = [(end, chunk), (8, l2)]
mmap_data_end.value = 8+l2
debug("sending damage with mmap: %s", data)
return data, mmap_free_size
7 changes: 5 additions & 2 deletions src/xpra/server/server_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,8 +533,11 @@ def parse_batch_int(value, varname):
mmap_file = capabilities.get("mmap_file")
mmap_token = capabilities.get("mmap_token")
log("client supplied mmap_file=%s, mmap supported=%s", mmap_file, self.supports_mmap)
if self.supports_mmap and mmap_file and os.path.exists(mmap_file):
self.init_mmap(mmap_file, mmap_token)
if mmap_file and os.path.exists(mmap_file):
if not self.supports_mmap:
log.warn("client supplied an mmap_file: %s but mmap mode is not supported", mmap_file)
else:
self.init_mmap(mmap_file, mmap_token)
log("cursors=%s, bell=%s, notifications=%s", self.send_cursors, self.send_bell, self.send_notifications)
log("client uuid %s", self.uuid)
msg = "%s %s client version %s" % (self.client_type, platform_name(self.client_platform, self.client_release), self.client_version)
Expand Down
78 changes: 3 additions & 75 deletions src/xpra/server/window_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
except:
from io import StringIO #@UnresolvedImport @Reimport
import time
import ctypes
from threading import Lock
from math import sqrt

Expand Down Expand Up @@ -384,9 +383,6 @@ def __init__(self, queue_damage, queue_packet, statistics,
# mmap:
self._mmap = mmap
self._mmap_size = mmap_size
if self._mmap and self._mmap_size>0:
self._mmap_data_start = ctypes.c_uint.from_buffer(self._mmap, 0)
self._mmap_data_end = ctypes.c_uint.from_buffer(self._mmap, 4)

# video codecs:
self._video_encoder = None
Expand Down Expand Up @@ -1096,82 +1092,14 @@ def video_encode(self, wid, x, y, w, h, coding, data, rowstride, options):
self._video_encoder_lock.release()

def mmap_send(self, coding, data):
from xpra.net.mmap_pipe import mmap_write
start = time.time()
mmap_data = self._mmap_send(data)
mmap_data, mmap_free_size = mmap_write(self._mmap, self._mmap_size, data)
self.global_statistics.mmap_free_size = mmap_free_size
elapsed = time.time()-start+0.000000001 #make sure never zero!
debug("%s MBytes/s - %s bytes written to mmap in %.1f ms", int(len(data)/elapsed/1024/1024), len(data), 1000*elapsed)
if mmap_data is not None:
self.global_statistics.mmap_bytes_sent += len(data)
coding = "mmap"
data = mmap_data
return coding, data

def _mmap_send(self, data):
"""
Sends 'data' to the client via the mmap shared memory region,
called by 'make_data_packet' from the non-UI thread 'data_to_packet'.
"""
#This is best explained using diagrams:
#mmap_area=[&S&E-------------data-------------]
#The first pair of 4 bytes are occupied by:
#S=data_start index is only updated by the client and tells us where it has read up to
#E=data_end index is only updated here and marks where we have written up to (matches current seek)
# '-' denotes unused/available space
# '+' is for data we have written
# '*' is for data we have just written in this call
# E and S show the location pointed to by data_start/data_end
start = max(8, self._mmap_data_start.value)
end = max(8, self._mmap_data_end.value)
if end<start:
#we have wrapped around but the client hasn't yet:
#[++++++++E--------------------S+++++]
#so there is one chunk available (from E to S):
available = start-end
chunk = available
else:
#we have not wrapped around yet, or the client has wrapped around too:
#[------------S++++++++++++E---------]
#so there are two chunks available (from E to the end, from the start to S):
chunk = self._mmap_size-end
available = chunk+(start-8)
l = len(data)
#update global mmap stats:
self.global_statistics.mmap_free_size = available-l
if l>=available:
warn("mmap area full: we need more than %s but only %s left! ouch!", l, available)
return None
if l<chunk:
""" data fits in the first chunk """
#ie: initially:
#[----------------------------------]
#[*********E------------------------]
#or if data already existed:
#[+++++++++E------------------------]
#[+++++++++**********E--------------]
self._mmap.seek(end)
self._mmap.write(data)
data = [(end, l)]
self._mmap_data_end.value = end+l
else:
""" data does not fit in first chunk alone """
if available>=(self._mmap_size/2) and available>=(l*3) and l<(start-8):
""" still plenty of free space, don't wrap around: just start again """
#[------------------S+++++++++E------]
#[*******E----------S+++++++++-------]
self._mmap.seek(8)
self._mmap.write(data)
data = [(8, l)]
self._mmap_data_end.value = 8+l
else:
""" split in 2 chunks: wrap around the end of the mmap buffer """
#[------------------S+++++++++E------]
#[******E-----------S+++++++++*******]
self._mmap.seek(end)
self._mmap.write(data[:chunk])
self._mmap.seek(8)
self._mmap.write(data[chunk:])
l2 = l-chunk
data = [(end, chunk), (8, l2)]
self._mmap_data_end.value = 8+l2
debug("sending damage with mmap: %s", data)
return data

0 comments on commit 2c3f04a

Please sign in to comment.