/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/_btree_serializer_pyx.pyx

  • Committer: Martin Pool
  • Date: 2007-10-03 08:06:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2901.
  • Revision ID: mbp@sourcefrog.net-20071003080644-oivy0gkg98sex0ed
Avoid internal error tracebacks on failure to lock on readonly transport (#129701).

Add new LockFailed, which doesn't imply that we failed to get it because of
contention.  Raise this if we fail to create the pending or lock directories
because of Transport errors.

UnlockableTransport is not an internal error.

ReadOnlyLockError has a message which didn't match its name or usage; it's now
deprecated and callers are updated to use LockFailed which is more appropriate.

Add zero_ninetytwo deprecation symbol.

Unify assertMatchesRe with TestCase.assertContainsRe.

When the constructor is deprecated, just say that the class is deprecated, not
the __init__ method - this works better with applyDeprecated in tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
#
17
 
 
18
 
"""Pyrex extensions to btree node parsing."""
19
 
 
20
 
#python2.4 support
21
 
cdef extern from "python-compat.h":
22
 
    pass
23
 
 
24
 
cdef extern from "stdlib.h":
25
 
    ctypedef unsigned size_t
26
 
 
27
 
cdef extern from "Python.h":
28
 
    ctypedef int Py_ssize_t # Required for older pyrex versions
29
 
    ctypedef struct PyObject:
30
 
        pass
31
 
    int PyList_Append(object lst, object item) except -1
32
 
 
33
 
    char *PyString_AsString(object p) except NULL
34
 
    object PyString_FromStringAndSize(char *, Py_ssize_t)
35
 
    PyObject *PyString_FromStringAndSize_ptr "PyString_FromStringAndSize" (char *, Py_ssize_t)
36
 
    int PyString_CheckExact(object s)
37
 
    int PyString_CheckExact_ptr "PyString_CheckExact" (PyObject *)
38
 
    Py_ssize_t PyString_Size(object p)
39
 
    Py_ssize_t PyString_GET_SIZE_ptr "PyString_GET_SIZE" (PyObject *)
40
 
    char * PyString_AS_STRING_ptr "PyString_AS_STRING" (PyObject *)
41
 
    char * PyString_AS_STRING(object)
42
 
    Py_ssize_t PyString_GET_SIZE(object)
43
 
    int PyString_AsStringAndSize_ptr(PyObject *, char **buf, Py_ssize_t *len)
44
 
    void PyString_InternInPlace(PyObject **)
45
 
    int PyTuple_CheckExact(object t)
46
 
    object PyTuple_New(Py_ssize_t n_entries)
47
 
    void PyTuple_SET_ITEM(object, Py_ssize_t offset, object) # steals the ref
48
 
    Py_ssize_t PyTuple_GET_SIZE(object t)
49
 
    PyObject *PyTuple_GET_ITEM_ptr_object "PyTuple_GET_ITEM" (object tpl, int index)
50
 
    void Py_INCREF(object)
51
 
    void Py_DECREF_ptr "Py_DECREF" (PyObject *)
52
 
 
53
 
cdef extern from "string.h":
54
 
    void *memcpy(void *dest, void *src, size_t n)
55
 
    void *memchr(void *s, int c, size_t n)
56
 
    # GNU extension
57
 
    # void *memrchr(void *s, int c, size_t n)
58
 
    int strncmp(char *s1, char *s2, size_t n)
59
 
 
60
 
# It seems we need to import the definitions so that the pyrex compiler has
61
 
# local names to access them.
62
 
from _static_tuple_c cimport StaticTuple, \
63
 
    import_static_tuple_c, StaticTuple_New, \
64
 
    StaticTuple_Intern, StaticTuple_SET_ITEM, StaticTuple_CheckExact
65
 
 
66
 
 
67
 
# TODO: Find some way to import this from _dirstate_helpers
68
 
cdef void* _my_memrchr(void *s, int c, size_t n): # cannot_raise
69
 
    # memrchr seems to be a GNU extension, so we have to implement it ourselves
70
 
    # It is not present in any win32 standard library
71
 
    cdef char *pos
72
 
    cdef char *start
73
 
 
74
 
    start = <char*>s
75
 
    pos = start + n - 1
76
 
    while pos >= start:
77
 
        if pos[0] == c:
78
 
            return <void*>pos
79
 
        pos = pos - 1
80
 
    return NULL
81
 
 
82
 
 
83
 
# TODO: Import this from _dirstate_helpers when it is merged
84
 
cdef object safe_string_from_size(char *s, Py_ssize_t size):
85
 
    if size < 0:
86
 
        raise AssertionError(
87
 
            'tried to create a string with an invalid size: %d @0x%x'
88
 
            % (size, <int>s))
89
 
    return PyString_FromStringAndSize(s, size)
90
 
 
91
 
 
92
 
cdef object safe_interned_string_from_size(char *s, Py_ssize_t size):
93
 
    cdef PyObject *py_str
94
 
    if size < 0:
95
 
        raise AssertionError(
96
 
            'tried to create a string with an invalid size: %d @0x%x'
97
 
            % (size, <int>s))
98
 
    py_str = PyString_FromStringAndSize_ptr(s, size)
99
 
    PyString_InternInPlace(&py_str)
100
 
    result = <object>py_str
101
 
    # Casting a PyObject* to an <object> triggers an INCREF from Pyrex, so we
102
 
    # DECREF it to avoid geting immortal strings
103
 
    Py_DECREF_ptr(py_str)
104
 
    return result
105
 
 
106
 
from bzrlib import _static_tuple_c
107
 
# This sets up the StaticTuple C_API functionality
108
 
import_static_tuple_c()
109
 
 
110
 
 
111
 
cdef class BTreeLeafParser:
112
 
    """Parse the leaf nodes of a BTree index.
113
 
 
114
 
    :ivar bytes: The PyString object containing the uncompressed text for the
115
 
        node.
116
 
    :ivar key_length: An integer describing how many pieces the keys have for
117
 
        this index.
118
 
    :ivar ref_list_length: An integer describing how many references this index
119
 
        contains.
120
 
    :ivar keys: A PyList of keys found in this node.
121
 
 
122
 
    :ivar _cur_str: A pointer to the start of the next line to parse
123
 
    :ivar _end_str: A pointer to the end of bytes
124
 
    :ivar _start: Pointer to the location within the current line while
125
 
        parsing.
126
 
    :ivar _header_found: True when we have parsed the header for this node
127
 
    """
128
 
 
129
 
    cdef object bytes
130
 
    cdef int key_length
131
 
    cdef int ref_list_length
132
 
    cdef object keys
133
 
 
134
 
    cdef char * _cur_str
135
 
    cdef char * _end_str
136
 
    # The current start point for parsing
137
 
    cdef char * _start
138
 
 
139
 
    cdef int _header_found
140
 
 
141
 
    def __init__(self, bytes, key_length, ref_list_length):
142
 
        self.bytes = bytes
143
 
        self.key_length = key_length
144
 
        self.ref_list_length = ref_list_length
145
 
        self.keys = []
146
 
        self._cur_str = NULL
147
 
        self._end_str = NULL
148
 
        self._header_found = 0
149
 
        # keys are tuples
150
 
 
151
 
    cdef extract_key(self, char * last):
152
 
        """Extract a key.
153
 
 
154
 
        :param last: points at the byte after the last byte permitted for the
155
 
            key.
156
 
        """
157
 
        cdef char *temp_ptr
158
 
        cdef int loop_counter
159
 
        cdef StaticTuple key
160
 
 
161
 
        key = StaticTuple_New(self.key_length)
162
 
        for loop_counter from 0 <= loop_counter < self.key_length:
163
 
            # grab a key segment
164
 
            temp_ptr = <char*>memchr(self._start, c'\0', last - self._start)
165
 
            if temp_ptr == NULL:
166
 
                if loop_counter + 1 == self.key_length:
167
 
                    # capture to last
168
 
                    temp_ptr = last
169
 
                else:
170
 
                    # Invalid line
171
 
                    failure_string = ("invalid key, wanted segment from " +
172
 
                        repr(safe_string_from_size(self._start,
173
 
                                                   last - self._start)))
174
 
                    raise AssertionError(failure_string)
175
 
            # capture the key string
176
 
            if (self.key_length == 1
177
 
                and (temp_ptr - self._start) == 45
178
 
                and strncmp(self._start, 'sha1:', 5) == 0):
179
 
                key_element = safe_string_from_size(self._start,
180
 
                                                    temp_ptr - self._start)
181
 
            else:
182
 
                key_element = safe_interned_string_from_size(self._start,
183
 
                                                         temp_ptr - self._start)
184
 
            # advance our pointer
185
 
            self._start = temp_ptr + 1
186
 
            Py_INCREF(key_element)
187
 
            StaticTuple_SET_ITEM(key, loop_counter, key_element)
188
 
        key = StaticTuple_Intern(key)
189
 
        return key
190
 
 
191
 
    cdef int process_line(self) except -1:
192
 
        """Process a line in the bytes."""
193
 
        cdef char *last
194
 
        cdef char *temp_ptr
195
 
        cdef char *ref_ptr
196
 
        cdef char *next_start
197
 
        cdef int loop_counter
198
 
        cdef Py_ssize_t str_len
199
 
 
200
 
        self._start = self._cur_str
201
 
        # Find the next newline
202
 
        last = <char*>memchr(self._start, c'\n', self._end_str - self._start)
203
 
        if last == NULL:
204
 
            # Process until the end of the file
205
 
            last = self._end_str
206
 
            self._cur_str = self._end_str
207
 
        else:
208
 
            # And the next string is right after it
209
 
            self._cur_str = last + 1
210
 
            # The last character is right before the '\n'
211
 
 
212
 
        if last == self._start:
213
 
            # parsed it all.
214
 
            return 0
215
 
        if last < self._start:
216
 
            # Unexpected error condition - fail
217
 
            raise AssertionError("last < self._start")
218
 
        if 0 == self._header_found:
219
 
            # The first line in a leaf node is the header "type=leaf\n"
220
 
            if strncmp("type=leaf", self._start, last - self._start) == 0:
221
 
                self._header_found = 1
222
 
                return 0
223
 
            else:
224
 
                raise AssertionError('Node did not start with "type=leaf": %r'
225
 
                    % (safe_string_from_size(self._start, last - self._start)))
226
 
 
227
 
        key = self.extract_key(last)
228
 
        # find the value area
229
 
        temp_ptr = <char*>_my_memrchr(self._start, c'\0', last - self._start)
230
 
        if temp_ptr == NULL:
231
 
            # Invalid line
232
 
            raise AssertionError("Failed to find the value area")
233
 
        else:
234
 
            # Because of how conversions were done, we ended up with *lots* of
235
 
            # values that are identical. These are all of the 0-length nodes
236
 
            # that are referred to by the TREE_ROOT (and likely some other
237
 
            # directory nodes.) For example, bzr has 25k references to
238
 
            # something like '12607215 328306 0 0', which ends up consuming 1MB
239
 
            # of memory, just for those strings.
240
 
            str_len = last - temp_ptr - 1
241
 
            if (str_len > 4
242
 
                and strncmp(" 0 0", last - 4, 4) == 0):
243
 
                # This drops peak mem for bzr.dev from 87.4MB => 86.2MB
244
 
                # For Launchpad 236MB => 232MB
245
 
                value = safe_interned_string_from_size(temp_ptr + 1, str_len)
246
 
            else:
247
 
                value = safe_string_from_size(temp_ptr + 1, str_len)
248
 
            # shrink the references end point
249
 
            last = temp_ptr
250
 
 
251
 
        if self.ref_list_length:
252
 
            ref_lists = StaticTuple_New(self.ref_list_length)
253
 
            loop_counter = 0
254
 
            while loop_counter < self.ref_list_length:
255
 
                ref_list = []
256
 
                # extract a reference list
257
 
                loop_counter = loop_counter + 1
258
 
                if last < self._start:
259
 
                    raise AssertionError("last < self._start")
260
 
                # find the next reference list end point:
261
 
                temp_ptr = <char*>memchr(self._start, c'\t', last - self._start)
262
 
                if temp_ptr == NULL:
263
 
                    # Only valid for the last list
264
 
                    if loop_counter != self.ref_list_length:
265
 
                        # Invalid line
266
 
                        raise AssertionError(
267
 
                            "invalid key, loop_counter != self.ref_list_length")
268
 
                    else:
269
 
                        # scan to the end of the ref list area
270
 
                        ref_ptr = last
271
 
                        next_start = last
272
 
                else:
273
 
                    # scan to the end of this ref list
274
 
                    ref_ptr = temp_ptr
275
 
                    next_start = temp_ptr + 1
276
 
                # Now, there may be multiple keys in the ref list.
277
 
                while self._start < ref_ptr:
278
 
                    # loop finding keys and extracting them
279
 
                    temp_ptr = <char*>memchr(self._start, c'\r',
280
 
                                             ref_ptr - self._start)
281
 
                    if temp_ptr == NULL:
282
 
                        # key runs to the end
283
 
                        temp_ptr = ref_ptr
284
 
 
285
 
                    PyList_Append(ref_list, self.extract_key(temp_ptr))
286
 
                ref_list = StaticTuple_Intern(StaticTuple(*ref_list))
287
 
                Py_INCREF(ref_list)
288
 
                StaticTuple_SET_ITEM(ref_lists, loop_counter - 1, ref_list)
289
 
                # prepare for the next reference list
290
 
                self._start = next_start
291
 
            node_value = StaticTuple(value, ref_lists)
292
 
        else:
293
 
            if last != self._start:
294
 
                # unexpected reference data present
295
 
                raise AssertionError("unexpected reference data present")
296
 
            node_value = StaticTuple(value, StaticTuple())
297
 
        PyList_Append(self.keys, StaticTuple(key, node_value))
298
 
        return 0
299
 
 
300
 
    def parse(self):
301
 
        cdef Py_ssize_t byte_count
302
 
        if not PyString_CheckExact(self.bytes):
303
 
            raise AssertionError('self.bytes is not a string.')
304
 
        byte_count = PyString_Size(self.bytes)
305
 
        self._cur_str = PyString_AsString(self.bytes)
306
 
        # This points to the last character in the string
307
 
        self._end_str = self._cur_str + byte_count
308
 
        while self._cur_str < self._end_str:
309
 
            self.process_line()
310
 
        return self.keys
311
 
 
312
 
 
313
 
def _parse_leaf_lines(bytes, key_length, ref_list_length):
314
 
    parser = BTreeLeafParser(bytes, key_length, ref_list_length)
315
 
    return parser.parse()
316
 
 
317
 
 
318
 
def _flatten_node(node, reference_lists):
319
 
    """Convert a node into the serialized form.
320
 
 
321
 
    :param node: A tuple representing a node:
322
 
        (index, key_tuple, value, references)
323
 
    :param reference_lists: Does this index have reference lists?
324
 
    :return: (string_key, flattened)
325
 
        string_key  The serialized key for referencing this node
326
 
        flattened   A string with the serialized form for the contents
327
 
    """
328
 
    cdef int have_reference_lists
329
 
    cdef Py_ssize_t flat_len
330
 
    cdef Py_ssize_t key_len
331
 
    cdef Py_ssize_t node_len
332
 
    cdef char * value
333
 
    cdef Py_ssize_t value_len
334
 
    cdef char * out
335
 
    cdef Py_ssize_t refs_len
336
 
    cdef Py_ssize_t next_len
337
 
    cdef int first_ref_list
338
 
    cdef int first_reference
339
 
    cdef int i
340
 
    cdef Py_ssize_t ref_bit_len
341
 
 
342
 
    if not PyTuple_CheckExact(node) and not StaticTuple_CheckExact(node):
343
 
        raise TypeError('We expected a tuple() or StaticTuple() for node not: %s'
344
 
            % type(node))
345
 
    node_len = len(node)
346
 
    have_reference_lists = reference_lists
347
 
    if have_reference_lists:
348
 
        if node_len != 4:
349
 
            raise ValueError('With ref_lists, we expected 4 entries not: %s'
350
 
                % len(node))
351
 
    elif node_len < 3:
352
 
        raise ValueError('Without ref_lists, we need at least 3 entries not: %s'
353
 
            % len(node))
354
 
    # TODO: We can probably do better than string.join(), namely
355
 
    #       when key has only 1 item, we can just grab that string
356
 
    #       And when there are 2 items, we could do a single malloc + len() + 1
357
 
    #       also, doing .join() requires a PyObject_GetAttrString call, which
358
 
    #       we could also avoid.
359
 
    # TODO: Note that pyrex 0.9.6 generates fairly crummy code here, using the
360
 
    #       python object interface, versus 0.9.8+ which uses a helper that
361
 
    #       checks if this supports the sequence interface.
362
 
    #       We *could* do more work on our own, and grab the actual items
363
 
    #       lists. For now, just ask people to use a better compiler. :)
364
 
    string_key = '\0'.join(node[1])
365
 
 
366
 
    # TODO: instead of using string joins, precompute the final string length,
367
 
    #       and then malloc a single string and copy everything in.
368
 
 
369
 
    # TODO: We probably want to use PySequenceFast, because we have lists and
370
 
    #       tuples, but we aren't sure which we will get.
371
 
 
372
 
    # line := string_key NULL flat_refs NULL value LF
373
 
    # string_key := BYTES (NULL BYTES)*
374
 
    # flat_refs := ref_list (TAB ref_list)*
375
 
    # ref_list := ref (CR ref)*
376
 
    # ref := BYTES (NULL BYTES)*
377
 
    # value := BYTES
378
 
    refs_len = 0
379
 
    if have_reference_lists:
380
 
        # Figure out how many bytes it will take to store the references
381
 
        ref_lists = node[3]
382
 
        next_len = len(ref_lists) # TODO: use a Py function
383
 
        if next_len > 0:
384
 
            # If there are no nodes, we don't need to do any work
385
 
            # Otherwise we will need (len - 1) '\t' characters to separate
386
 
            # the reference lists
387
 
            refs_len = refs_len + (next_len - 1)
388
 
            for ref_list in ref_lists:
389
 
                next_len = len(ref_list)
390
 
                if next_len > 0:
391
 
                    # We will need (len - 1) '\r' characters to separate the
392
 
                    # references
393
 
                    refs_len = refs_len + (next_len - 1)
394
 
                    for reference in ref_list:
395
 
                        if (not PyTuple_CheckExact(reference)
396
 
                            and not StaticTuple_CheckExact(reference)):
397
 
                            raise TypeError(
398
 
                                'We expect references to be tuples not: %s'
399
 
                                % type(reference))
400
 
                        next_len = len(reference)
401
 
                        if next_len > 0:
402
 
                            # We will need (len - 1) '\x00' characters to
403
 
                            # separate the reference key
404
 
                            refs_len = refs_len + (next_len - 1)
405
 
                            for ref_bit in reference:
406
 
                                if not PyString_CheckExact(ref_bit):
407
 
                                    raise TypeError('We expect reference bits'
408
 
                                        ' to be strings not: %s'
409
 
                                        % type(<object>ref_bit))
410
 
                                refs_len = refs_len + PyString_GET_SIZE(ref_bit)
411
 
 
412
 
    # So we have the (key NULL refs NULL value LF)
413
 
    key_len = PyString_Size(string_key)
414
 
    val = node[2]
415
 
    if not PyString_CheckExact(val):
416
 
        raise TypeError('Expected a plain str for value not: %s'
417
 
                        % type(val))
418
 
    value = PyString_AS_STRING(val)
419
 
    value_len = PyString_GET_SIZE(val)
420
 
    flat_len = (key_len + 1 + refs_len + 1 + value_len + 1)
421
 
    line = PyString_FromStringAndSize(NULL, flat_len)
422
 
    # Get a pointer to the new buffer
423
 
    out = PyString_AsString(line)
424
 
    memcpy(out, PyString_AsString(string_key), key_len)
425
 
    out = out + key_len
426
 
    out[0] = c'\0'
427
 
    out = out + 1
428
 
    if refs_len > 0:
429
 
        first_ref_list = 1
430
 
        for ref_list in ref_lists:
431
 
            if first_ref_list == 0:
432
 
                out[0] = c'\t'
433
 
                out = out + 1
434
 
            first_ref_list = 0
435
 
            first_reference = 1
436
 
            for reference in ref_list:
437
 
                if first_reference == 0:
438
 
                    out[0] = c'\r'
439
 
                    out = out + 1
440
 
                first_reference = 0
441
 
                next_len = len(reference)
442
 
                for i from 0 <= i < next_len:
443
 
                    if i != 0:
444
 
                        out[0] = c'\x00'
445
 
                        out = out + 1
446
 
                    ref_bit = reference[i]
447
 
                    ref_bit_len = PyString_GET_SIZE(ref_bit)
448
 
                    memcpy(out, PyString_AS_STRING(ref_bit), ref_bit_len)
449
 
                    out = out + ref_bit_len
450
 
    out[0] = c'\0'
451
 
    out = out  + 1
452
 
    memcpy(out, value, value_len)
453
 
    out = out + value_len
454
 
    out[0] = c'\n'
455
 
    return string_key, line