7

I want to read a file, 4 lines by 4 (it's a fastq file, with DNA sequences).
When I read the file one line by one or two by two, there's no issues, but when I read 3 or 4 lines at once, my code crashes (kernel appeared to have died on jupyter notebook). (Uncommenting the last part, or any 3 out of the 4 getline().
I tried with a double array of char (char**) to store the lines, with the same issue.

Any idea what can be the cause ?

Using Python 3.7.3, Cython 0.29, all other libraries updated. File being read is about 1.3GB, machine has 8GB, ubuntu 16.04. Code adapted from https://gist.github.com/pydemo/0b85bd5d1c017f6873422e02aeb9618a

%%cython
from libc.stdio cimport FILE, fopen, fclose, getline
    
def fastq_reader(early_stop=10):
    cdef const char* fname = b'/path/to/file'
    cdef FILE* cfile
    cfile = fopen(fname, "rb")

    cdef:
        char * line_0 = NULL
        char * line_1 = NULL
        char * line_2 = NULL
        char * line_3 = NULL
        size_t seed = 0
        ssize_t length_line
        unsigned long long line_nb = 0

    while True:
        length_line = getline(&line_0, &seed, cfile)
        if length_line < 0: break
        
        length_line = getline(&line_1, &seed, cfile)
        if length_line < 0: break
        
#         length_line = getline(&line_2, &seed, cfile)
#         if length_line < 0: break
        
#         length_line = getline(&line_3, &seed, cfile)
#         if length_line < 0: break

        line_nb += 4
        if line_nb > early_stop:
            break

    fclose(cfile)
    return line_nb

fastq_reader(early_stop=20000)
Sylvain
  • 355
  • 4
  • 6
  • 2
    What does the value contained in `seed` tell `getline`? – DavidW Apr 25 '21 at 20:38
  • Everytime you call getline with null pointer, n (or seed in your case) should be 0, but isn’t – ead Apr 25 '21 at 20:39
  • Thanks guys, found it out. I misunderstood the second argument of getline(). That "seed" is actually the buffer size, that is being resized by getline(). So a different variable for each line is necessary. – Sylvain Apr 25 '21 at 22:02

1 Answers1

1

The underlying problem was my misunderstanding of getline() getline() c reference

To store lines in different variables, an associated n is necessary for each line pointer *lineptr.

If *lineptr is set to NULL and *n is set 0 before the call, then getline() will allocate a buffer for storing the line.

Alternatively, before calling getline(), *lineptr can contain a pointer to a malloc(3)-allocated buffer *n bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc(3), updating *lineptr and *n as necessary.

The n (or seed in my code) will hold the size of the buffer allocated for the pointer, where getline() puts the incoming line. As I set the same buffer variable for different pointers, getline was given the wrong information of the size of the char* line_xxx.


As fastq files are usually in this shape:

@read_id_usually_short
CTATACCACCAAGGCTGGAAATTGTAAAACACACCGCCTGACATATCAATAAGGTGTCAAATTCCCTTTTCTCTAGCTTTCGTACT_very_long
+
-///.)/.-/)//-//..-*...-.&%&.--%#(++*/.//////,/*//+(.///..,%&-#&)..,)/.,.._same_length_as_line_2

There was no error for one or two getline() with the same buffer length, as the buffer was too small and getline sized-up the pointers.
But when using 3 or 4 getlines(), the call to length_line = getline(&line_2, &seed, cfile) was asked to store a char* of length 2 ('+\n'), while getting (the wrong information) that the pointer line_2 was already big enough (size of line_1).


So the (simple) solution is

%%cython
from libc.stdio cimport FILE, fopen, fclose, getline
    
def fastq_reader(early_stop=10):
    cdef const char* fname = b'/path/to/file'
    cdef FILE* cfile
    cfile = fopen(fname, "rb")

    cdef:
        char * line_0 = NULL
        char * line_1 = NULL
        char * line_2 = NULL
        char * line_3 = NULL
        # One variable for each line pointer
        size_t n_0 = 0
        size_t n_1 = 0
        size_t n_2 = 0
        size_t n_3 = 0
        ssize_t length_line
        unsigned long long line_nb = 0

    while True:
        # Reading the same file (same cfile), but line_x and n_x by pairs)
        length_line = getline(&line_0, &n_0, cfile)  
        if length_line < 0: break
        
        length_line = getline(&line_1, &n_1, cfile)
        if length_line < 0: break
        
        length_line = getline(&line_2, &n_2, cfile)
        if length_line < 0: break
        
        length_line = getline(&line_3, &n_3, cfile)
        if length_line < 0: break

        line_nb += 4
        if line_nb > early_stop:
            break

    fclose(cfile)
    return line_nb

fastq_reader(early_stop=20000)

Thanks for pointing out my mistake.

Sylvain
  • 355
  • 4
  • 6