[Python-Dev] writelines() not thread-safe

Guido van Rossum guido@python.org
Fri, 10 Mar 2000 10:06:48 -0500


OK, here's a patch for writelines() that supports arbitrary sequences
and fixes the lock problem using Tim's solution #1.5 (slicing 1000
items at a time).  It contains a fast path for when the argument is a
list, using PyList_GetSlice; otherwise it uses PyObject_GetItem and a
fixed list.

Please have a good look at this; I've only tested it lightly.

--Guido van Rossum (home page: http://www.python.org/~guido/)

Index: fileobject.c
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Objects/fileobject.c,v
retrieving revision 2.70
diff -c -r2.70 fileobject.c
*** fileobject.c	2000/02/29 13:59:28	2.70
--- fileobject.c	2000/03/10 14:55:47
***************
*** 884,923 ****
  	PyFileObject *f;
  	PyObject *args;
  {
! 	int i, n;
  	if (f->f_fp == NULL)
  		return err_closed();
! 	if (args == NULL || !PyList_Check(args)) {
  		PyErr_SetString(PyExc_TypeError,
! 			   "writelines() requires list of strings");
  		return NULL;
  	}
! 	n = PyList_Size(args);
! 	f->f_softspace = 0;
! 	Py_BEGIN_ALLOW_THREADS
! 	errno = 0;
! 	for (i = 0; i < n; i++) {
! 		PyObject *line = PyList_GetItem(args, i);
! 		int len;
! 		int nwritten;
! 		if (!PyString_Check(line)) {
! 			Py_BLOCK_THREADS
! 			PyErr_SetString(PyExc_TypeError,
! 				   "writelines() requires list of strings");
  			return NULL;
  		}
! 		len = PyString_Size(line);
! 		nwritten = fwrite(PyString_AsString(line), 1, len, f->f_fp);
! 		if (nwritten != len) {
! 			Py_BLOCK_THREADS
! 			PyErr_SetFromErrno(PyExc_IOError);
! 			clearerr(f->f_fp);
! 			return NULL;
  		}
  	}
! 	Py_END_ALLOW_THREADS
  	Py_INCREF(Py_None);
! 	return Py_None;
  }
  
  static PyMethodDef file_methods[] = {
--- 884,975 ----
  	PyFileObject *f;
  	PyObject *args;
  {
! #define CHUNKSIZE 1000
! 	PyObject *list, *line;
! 	PyObject *result;
! 	int i, j, index, len, nwritten, islist;
! 
  	if (f->f_fp == NULL)
  		return err_closed();
! 	if (args == NULL || !PySequence_Check(args)) {
  		PyErr_SetString(PyExc_TypeError,
! 			   "writelines() requires sequence of strings");
  		return NULL;
  	}
! 	islist = PyList_Check(args);
! 
! 	/* Strategy: slurp CHUNKSIZE lines into a private list,
! 	   checking that they are all strings, then write that list
! 	   without holding the interpreter lock, then come back for more. */
! 	index = 0;
! 	if (islist)
! 		list = NULL;
! 	else {
! 		list = PyList_New(CHUNKSIZE);
! 		if (list == NULL)
  			return NULL;
+ 	}
+ 	result = NULL;
+ 
+ 	for (;;) {
+ 		if (islist) {
+ 			Py_XDECREF(list);
+ 			list = PyList_GetSlice(args, index, index+CHUNKSIZE);
+ 			if (list == NULL)
+ 				return NULL;
+ 			j = PyList_GET_SIZE(list);
  		}
! 		else {
! 			for (j = 0; j < CHUNKSIZE; j++) {
! 				line = PySequence_GetItem(args, index+j);
! 				if (line == NULL) {
! 					if (PyErr_ExceptionMatches(PyExc_IndexError)) {
! 						PyErr_Clear();
! 						break;
! 					}
! 					/* Some other error occurred.
! 					   Note that we may lose some output. */
! 					goto error;
! 				}
! 				if (!PyString_Check(line)) {
! 					PyErr_SetString(PyExc_TypeError,
! 					 "writelines() requires sequences of strings");
! 					goto error;
! 				}
! 				PyList_SetItem(list, j, line);
! 			}
! 		}
! 		if (j == 0)
! 			break;
! 
! 		Py_BEGIN_ALLOW_THREADS
! 		f->f_softspace = 0;
! 		errno = 0;
! 		for (i = 0; i < j; i++) {
! 			line = PyList_GET_ITEM(list, i);
! 			len = PyString_GET_SIZE(line);
! 			nwritten = fwrite(PyString_AS_STRING(line),
! 					  1, len, f->f_fp);
! 			if (nwritten != len) {
! 				Py_BLOCK_THREADS
! 				PyErr_SetFromErrno(PyExc_IOError);
! 				clearerr(f->f_fp);
! 				Py_DECREF(list);
! 				return NULL;
! 			}
  		}
+ 		Py_END_ALLOW_THREADS
+ 
+ 		if (j < CHUNKSIZE)
+ 			break;
+ 		index += CHUNKSIZE;
  	}
! 
  	Py_INCREF(Py_None);
! 	result = Py_None;
!   error:
! 	Py_XDECREF(list);
! 	return result;
  }
  
  static PyMethodDef file_methods[] = {