Should the program write log files unlocked

Keywords: Linux PHP Nginx

log

To make my thoughts clearer, I'll call the log below.Because the word log has two meanings, see Baidu Encyclopedia Explanation perhaps Wikipedia Explanation.

  • diary Another way of saying this.The word "journal" itself means "record". A journal is a daily record (usually related to the author).
  • Server Log (server log) records the operation of computer devices or software such as servers.

Of course what we're talking about here is the server log, which is the server log.

Write log

Typically, writing a log follows these steps:

int fd = open(path)
write(fd, sign_append) fclose(fd)

 

Explain the code above:

1. int fd = open(path) 

A file descriptor is opened through a system call, or in other languages it can be called a resource descriptor, resource type, or handle.

 

2. write(fd, append = 1)

write system calls, with the append flag, execute both seek and write system calls, but these system calls are atomic.

Atomicity means that seek and write execute simultaneously, and there will be no crossover between two threads. a thread must execute both seek and write before b thread can continue execution (in this case, threads, because threads are the basic unit of cpu scheduling).

So in nginx, we add the append flag so that we don't have to lock threads.

 

3. fclose(fd)

Close the descriptor.

linux generally has a maximum number of open file descriptors, which can cause large bug s if not turned off.

There are several ways to see the limitations in linux (where open files represent the number of files that can be opened):

$ ulimit -a

core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15732
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15732
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

 

So if it's a system call, append doesn't need to be locked.

 

Why should append be locked when php is writing logs?

If you follow the above instructions, we can set the append flag of the write, and then we can fall asleep and the files will never conflict.

But (generally there is one but) frames where you go to php are locked before the append of file_put_contents.

Therefore, it is suspected that the underlying implementation of file_put_contents does not achieve atomicity.

Follow-up source (non-php programmers or those who are not interested in PHP underlying source can skip):

The underlying implementation of file_put_contents:

// file.c
/* {{{ proto int|false file_put_contents(string file, mixed data [, int flags [, resource context]])
   Write/Create a file with contents data and return the number of bytes written */
PHP_FUNCTION(file_put_contents)
{
...
case IS_STRING:
   if (Z_STRLEN_P(data)) {
      numbytes = php_stream_write(stream, Z_STRVAL_P(data), Z_STRLEN_P(data));
      if (numbytes != Z_STRLEN_P(data)) {
         php_error_docref(NULL, E_WARNING, "Only %zd of %zd bytes written, possibly out of free disk space", numbytes, Z_STRLEN_P(data));
         numbytes = -1;
      }
   }
   break;
...
}

// php_streams.h
PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count);
#define php_stream_write_string(stream, str)   _php_stream_write(stream, str, strlen(str))
#define php_stream_write(stream, buf, count)   _php_stream_write(stream, (buf), (count))

// streams.c
PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count)
{
  ...
   if (stream->writefilters.head) {
      bytes = _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL);
   } else {
      bytes = _php_stream_write_buffer(stream, buf, count);
   }

   if (bytes) {
      stream->flags |= PHP_STREAM_FLAG_WAS_WRITTEN;
   }

   return bytes;
}

/* Writes a buffer directly to a stream, using multiple of the chunk size */
static ssize_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count){
...
while (count > 0) {
   ssize_t justwrote = stream->ops->write(stream, buf, count);
   if (justwrote <= 0) {
      /* If we already successfully wrote some bytes and a write error occurred
       * later, report the successfully written bytes. */
      if (didwrite == 0) {
         return justwrote;
      }
      return didwrite;
   }

   buf += justwrote;
   count -= justwrote;
   didwrite += justwrote;

   /* Only screw with the buffer if we can seek, otherwise we lose data
    * buffered from fifos and sockets */
   if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
      stream->position += justwrote;
   }
}

}

// php_streams.h
/* operations on streams that are file-handles */
typedef struct _php_stream_ops  {
   /* stdio like functions - these are mandatory! */
   ssize_t (*write)(php_stream *stream, const char *buf, size_t count);
   ssize_t (*read)(php_stream *stream, char *buf, size_t count);
   int    (*close)(php_stream *stream, int close_handle);
   int    (*flush)(php_stream *stream);

   const char *label; /* label for this ops structure */

   /* these are optional */
   int (*seek)(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset);
   int (*cast)(php_stream *stream, int castas, void **ret);
   int (*stat)(php_stream *stream, php_stream_statbuf *ssb);
   int (*set_option)(php_stream *stream, int option, int value, void *ptrparam);
} php_stream_ops;
 
// plain_wrapper.c
static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
{
   php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;

   assert(data != NULL);

   if (data->fd >= 0) {
#ifdef PHP_WIN32
      ssize_t bytes_written;
      if (ZEND_SIZE_T_UINT_OVFL(count)) {
         count = UINT_MAX;
      }
      bytes_written = _write(data->fd, buf, (unsigned int)count);
#else
      ssize_t bytes_written = write(data->fd, buf, count);
#endif
      if (bytes_written < 0) {
         if (errno == EWOULDBLOCK || errno == EAGAIN) {
            return 0;
         }
         if (errno == EINTR) {
            /* TODO: Should this be treated as a proper error or not? */
            return bytes_written;
         }
         php_error_docref(NULL, E_NOTICE, "write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno));
      }
      return bytes_written;
   } else {

#if HAVE_FLUSHIO
      if (data->is_seekable && data->last_op == 'r') {
         zend_fseek(data->file, 0, SEEK_CUR);
      }
      data->last_op = 'w';
#endif

      return (ssize_t) fwrite(buf, 1, count, data->file);
   }
}

 

This function eventually calls the function php_stdiop_write

The string is divided into chunksize s in the function_php_stream_write_buffer, each of which is 8096 bytes, and written separately.

If you do not lock, then after more than 8096 bytes, multiple processes will get confused writing logs.

Also, the php documentation explains:

 

 

 

So, ultimately, it needs to be analyzed in different languages.

Posted by cetaces on Sat, 28 Dec 2019 12:46:51 -0800