source: trunk/libtransmission/fdlimit.c @ 7801

Last change on this file since 7801 was 7801, checked in by charles, 13 years ago

(trunk libT) fsync busy files every 15 seconds or so. On linux, use posix_fadvise() to tell the system no to cache torrent blocks

  • Property svn:keywords set to Date Rev Author Id
File size: 16.0 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 7801 2009-01-26 00:36:34Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#ifndef WIN32
26 #define HAVE_GETRLIMIT
27#endif
28
29#ifdef HAVE_POSIX_FADVISE
30 #ifdef _XOPEN_SOURCE
31  #undef _XOPEN_SOURCE
32 #endif
33 #define _XOPEN_SOURCE 600
34#endif
35
36#include <assert.h>
37#include <errno.h>
38#include <inttypes.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#ifdef SYS_DARWIN
43 #include <fcntl.h>
44#endif
45
46#ifdef HAVE_FALLOCATE
47 #include <linux/falloc.h>
48#endif
49
50#include <sys/types.h>
51#include <sys/stat.h>
52#ifdef HAVE_GETRLIMIT
53 #include <sys/time.h> /* getrlimit */
54 #include <sys/resource.h> /* getrlimit */
55#endif
56#include <unistd.h>
57#include <fcntl.h> /* O_LARGEFILE posix_fadvise */
58
59#include <evutil.h>
60
61#include "transmission.h"
62#include "fdlimit.h"
63#include "list.h"
64#include "net.h"
65#include "platform.h" /* tr_lock */
66#include "utils.h"
67
68#define dbgmsg( ... ) \
69    do { \
70        if( tr_deepLoggingIsActive( ) ) \
71            tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
72    } while( 0 )
73
74/**
75***
76**/
77
78enum
79{
80    NOFILE_BUFFER = 512, /* the process' number of open files is
81                            globalMaxPeers + NOFILE_BUFFER */
82
83    SYNC_INTERVAL = 15   /* (arbitrary number) how many seconds to go
84                            between fsync calls for files in heavy use */
85};
86
87struct tr_openfile
88{
89    tr_bool    isCheckedOut;
90    tr_bool    isWritable;
91    tr_bool    closeWhenDone;
92    char       filename[MAX_PATH_LENGTH];
93    int        fd;
94    uint64_t   date;
95    time_t     syncAt;
96};
97
98struct tr_fd_s
99{
100    int                   socketCount;
101    int                   socketLimit;
102
103    struct tr_openfile  * openFiles;
104    int                   openFileLimit;
105
106    tr_lock             * lock;
107};
108
109static struct tr_fd_s * gFd = NULL;
110
111/***
112****
113****  Local Files
114****
115***/
116
117#ifndef O_LARGEFILE
118 #define O_LARGEFILE 0
119#endif
120
121static tr_bool
122preallocateFileSparse( int fd, uint64_t length )
123{
124    const char zero = '\0';
125
126    if( length == 0 )
127        return TRUE;
128
129    if( lseek( fd, length-1, SEEK_SET ) == -1 )
130        return FALSE;
131    if( write( fd, &zero, 1 ) == -1 )
132        return FALSE;
133    if( ftruncate( fd, length ) == -1 )
134        return FALSE;
135
136    return TRUE;
137}
138
139static tr_bool
140preallocateFileFull( const char * filename, uint64_t length )
141{
142    tr_bool success = 0;
143
144#ifdef WIN32
145
146    HANDLE hFile = CreateFile( filename, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0 );
147    if( hFile != INVALID_HANDLE_VALUE )
148    {
149        LARGE_INTEGER li;
150        li.QuadPart = length;
151        success = SetFilePointerEx( hFile, li, NULL, FILE_BEGIN ) && SetEndOfFile( hFile );
152        CloseHandle( hFile );
153    }
154
155#else
156
157    int flags = O_RDWR | O_CREAT | O_LARGEFILE;
158    int fd = open( filename, flags, 0666 );
159    if( fd >= 0 )
160    {
161       
162# ifdef HAVE_FALLOCATE
163
164        success = !fallocate( fd, FALLOC_FL_KEEP_SIZE, 0, length );
165
166# elif defined(HAVE_POSIX_FALLOCATE)
167
168        success = !posix_fallocate( fd, 0, length );
169
170# elif defined(SYS_DARWIN)
171
172        fstore_t fst;
173        fst.fst_flags = F_ALLOCATECONTIG;
174        fst.fst_posmode = F_PEOFPOSMODE;
175        fst.fst_offset = 0;
176        fst.fst_length = length;
177        fst.fst_bytesalloc = 0;
178        success = !fcntl( fd, F_PREALLOCATE, &fst );
179
180# else
181
182        #warning no known method to preallocate files on this platform
183        success = 0;
184
185# endif
186
187        close( fd );
188    }
189
190#endif
191
192    return success;
193}
194
195/**
196 * returns 0 on success, or an errno value on failure.
197 * errno values include ENOENT if the parent folder doesn't exist,
198 * plus the errno values set by tr_mkdirp() and open().
199 */
200static int
201TrOpenFile( int                      i,
202            const char             * folder,
203            const char             * torrentFile,
204            tr_bool                  doWrite,
205            tr_preallocation_mode    preallocationMode,
206            uint64_t                 desiredFileSize )
207{
208    struct tr_openfile * file = &gFd->openFiles[i];
209    int                  flags;
210    char               * filename;
211    struct stat          sb;
212    int                  alreadyExisted;
213
214    /* confirm the parent folder exists */
215    if( stat( folder, &sb ) || !S_ISDIR( sb.st_mode ) )
216    {
217        tr_err( _( "Couldn't create \"%1$s\": parent folder \"%2$s\" does not exist" ), torrentFile, folder );
218        return ENOENT;
219    }
220
221    /* create subfolders, if any */
222    filename = tr_buildPath( folder, torrentFile, NULL );
223    if( doWrite )
224    {
225        char * tmp = tr_dirname( filename );
226        const int err = tr_mkdirp( tmp, 0777 ) ? errno : 0;
227        if( err ) {
228            tr_err( _( "Couldn't create \"%1$s\": %2$s" ), tmp, tr_strerror( err ) );
229            tr_free( tmp );
230            tr_free( filename );
231            return err;
232        }
233        tr_free( tmp );
234    }
235
236    alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
237
238    if( doWrite && !alreadyExisted && ( preallocationMode == TR_PREALLOCATE_FULL ) )
239        if( preallocateFileFull( filename, desiredFileSize ) )
240            tr_inf( _( "Preallocated file \"%s\"" ), filename );
241   
242    /* open the file */
243    flags = doWrite ? ( O_RDWR | O_CREAT ) : O_RDONLY;
244#ifdef O_RANDOM
245    flags |= O_RANDOM
246#endif
247#ifdef O_LARGEFILE
248    flags |= O_LARGEFILE;
249#endif
250#ifdef WIN32
251    flags |= O_BINARY;
252#endif
253    file->fd = open( filename, flags, 0666 );
254    if( file->fd == -1 )
255    {
256        const int err = errno;
257        tr_err( _( "Couldn't open \"%1$s\": %2$s" ), filename, tr_strerror( err ) );
258        tr_free( filename );
259        return err;
260    }
261
262    if( doWrite && !alreadyExisted && ( preallocationMode == TR_PREALLOCATE_SPARSE ) )
263        preallocateFileSparse( file->fd, desiredFileSize );
264
265#if defined( SYS_DARWIN )
266    fcntl( file->fd, F_NOCACHE, 1 );
267    fcntl( file->fd, F_RDAHEAD, 0 );
268#elif defined( HAVE_POSIX_FADVISE )
269    posix_fadvise( file->fd, 0, 0, POSIX_FADV_RANDOM );
270#endif
271
272    tr_free( filename );
273    return 0;
274}
275
276static int
277fileIsOpen( const struct tr_openfile * o )
278{
279    return o->fd >= 0;
280}
281
282static void
283TrCloseFile( int i )
284{
285    struct tr_openfile * o = &gFd->openFiles[i];
286
287    assert( i >= 0 );
288    assert( i < gFd->openFileLimit );
289    assert( fileIsOpen( o ) );
290
291    close( o->fd );
292    o->fd = -1;
293    o->isCheckedOut = 0;
294}
295
296static int
297fileIsCheckedOut( const struct tr_openfile * o )
298{
299    return fileIsOpen( o ) && o->isCheckedOut;
300}
301
302/* returns an fd on success, or a -1 on failure and sets errno */
303int
304tr_fdFileCheckout( const char             * folder,
305                   const char             * torrentFile,
306                   tr_bool                  doWrite,
307                   tr_preallocation_mode    preallocationMode,
308                   uint64_t                 desiredFileSize )
309{
310    int i, winner = -1;
311    struct tr_openfile * o;
312    char filename[MAX_PATH_LENGTH];
313
314    assert( folder && *folder );
315    assert( torrentFile && *torrentFile );
316    assert( doWrite == 0 || doWrite == 1 );
317
318    tr_snprintf( filename, sizeof( filename ), "%s%c%s", folder, TR_PATH_DELIMITER, torrentFile );
319    dbgmsg( "looking for file '%s', writable %c", filename, doWrite ? 'y' : 'n' );
320
321    tr_lockLock( gFd->lock );
322
323    /* Is it already open? */
324    for( i = 0; i < gFd->openFileLimit; ++i )
325    {
326        o = &gFd->openFiles[i];
327
328        if( !fileIsOpen( o ) )
329            continue;
330
331        if( strcmp( filename, o->filename ) )
332            continue;
333
334        if( fileIsCheckedOut( o ) )
335        {
336            dbgmsg( "found it!  it's open, but checked out.  waiting..." );
337            tr_lockUnlock( gFd->lock );
338            tr_wait( 200 );
339            tr_lockLock( gFd->lock );
340            i = -1; /* reloop */
341            continue;
342        }
343
344        if( doWrite && !o->isWritable )
345        {
346            dbgmsg(
347                "found it!  it's open and available, but isn't writable. closing..." );
348            TrCloseFile( i );
349            break;
350        }
351
352        dbgmsg( "found it!  it's ready for use!" );
353        winner = i;
354        break;
355    }
356
357    dbgmsg(
358        "it's not already open.  looking for an open slot or an old file." );
359    while( winner < 0 )
360    {
361        uint64_t date = tr_date( ) + 1;
362
363        /* look for the file that's been open longest */
364        for( i = 0; i < gFd->openFileLimit; ++i )
365        {
366            o = &gFd->openFiles[i];
367
368            if( !fileIsOpen( o ) )
369            {
370                winner = i;
371                dbgmsg( "found an empty slot in %d", winner );
372                break;
373            }
374
375            if( date > o->date )
376            {
377                date = o->date;
378                winner = i;
379            }
380        }
381
382        if( winner >= 0 )
383        {
384            if( fileIsOpen( &gFd->openFiles[winner] ) )
385            {
386                dbgmsg( "closing file '%s', slot #%d",
387                        gFd->openFiles[winner].filename,
388                        winner );
389                TrCloseFile( winner );
390            }
391        }
392        else
393        {
394            dbgmsg( "everything's full!  waiting for someone else to finish something" );
395            tr_lockUnlock( gFd->lock );
396            tr_wait( 200 );
397            tr_lockLock( gFd->lock );
398        }
399    }
400
401    assert( winner >= 0 );
402    o = &gFd->openFiles[winner];
403    if( !fileIsOpen( o ) )
404    {
405        const int err = TrOpenFile( winner, folder, torrentFile, doWrite, preallocationMode, desiredFileSize );
406        if( err ) {
407            tr_lockUnlock( gFd->lock );
408            errno = err;
409            return -1;
410        }
411
412        dbgmsg( "opened '%s' in slot %d, doWrite %c", filename, winner,
413                doWrite ? 'y' : 'n' );
414        tr_strlcpy( o->filename, filename, sizeof( o->filename ) );
415        o->isWritable = doWrite;
416        o->syncAt = time( NULL ) + SYNC_INTERVAL;
417    }
418
419    dbgmsg( "checking out '%s' in slot %d", filename, winner );
420    o->isCheckedOut = 1;
421    o->closeWhenDone = 0;
422    o->date = tr_date( );
423    tr_lockUnlock( gFd->lock );
424    return o->fd;
425}
426
427void
428tr_fdFileReturn( int fd )
429{
430    int i;
431
432    tr_lockLock( gFd->lock );
433
434    for( i = 0; i < gFd->openFileLimit; ++i )
435    {
436        struct tr_openfile * o = &gFd->openFiles[i];
437        if( o->fd != fd )
438            continue;
439
440        dbgmsg( "releasing file '%s' in slot #%d", o->filename, i );
441        o->isCheckedOut = 0;
442        if( o->closeWhenDone )
443            TrCloseFile( i );
444        else if( o->syncAt <= time( NULL ) ) {
445            dbgmsg( "fsync()ing file '%s' in slot #%d", o->filename, i );
446            fsync( o->fd );
447#ifdef HAVE_POSIX_FADVISE
448            /* TODO: test performance with and without this */
449            posix_fadvise( o->fd, 0, 0, POSIX_FADV_DONTNEED );
450#endif
451            o->syncAt = time( NULL ) + SYNC_INTERVAL;
452        }
453
454        break;
455    }
456
457    tr_lockUnlock( gFd->lock );
458}
459
460void
461tr_fdFileClose( const char * filename )
462{
463    int i;
464
465    tr_lockLock( gFd->lock );
466
467    for( i = 0; i < gFd->openFileLimit; ++i )
468    {
469        struct tr_openfile * o = &gFd->openFiles[i];
470        if( !fileIsOpen( o ) || strcmp( filename, o->filename ) )
471            continue;
472
473        dbgmsg( "tr_fdFileClose closing '%s'", filename );
474
475        if( !o->isCheckedOut )
476        {
477            dbgmsg( "not checked out, so closing it now... '%s'", filename );
478            TrCloseFile( i );
479        }
480        else
481        {
482            dbgmsg(
483                "flagging file '%s', slot #%d to be closed when checked in",
484                gFd->openFiles[i].filename, i );
485            o->closeWhenDone = 1;
486        }
487    }
488
489    tr_lockUnlock( gFd->lock );
490}
491
492/***
493****
494****  Sockets
495****
496***/
497
498static int
499getSocketMax( struct tr_fd_s * gFd )
500{
501    return gFd->socketLimit;
502}
503
504int
505tr_fdSocketCreate( int domain, int type )
506{
507    int s = -1;
508
509    tr_lockLock( gFd->lock );
510
511    if( gFd->socketCount < getSocketMax( gFd ) )
512        if( ( s = socket( domain, type, 0 ) ) < 0 )
513        {
514#ifdef SYS_DARWIN
515            if( sockerrno != EAFNOSUPPORT )
516#endif
517            tr_err( _( "Couldn't create socket: %s" ),
518                   tr_strerror( sockerrno ) );
519            s = -sockerrno;
520        }
521
522    if( s > -1 )
523        ++gFd->socketCount;
524
525    assert( gFd->socketCount >= 0 );
526
527    tr_lockUnlock( gFd->lock );
528    return s;
529}
530
531int
532tr_fdSocketAccept( int           b,
533                   tr_address  * addr,
534                   tr_port     * port )
535{
536    int                s = -1;
537    unsigned int       len;
538    struct sockaddr_storage sock;
539
540    assert( addr );
541    assert( port );
542
543    tr_lockLock( gFd->lock );
544    if( gFd->socketCount < getSocketMax( gFd ) )
545    {
546        len = sizeof( struct sockaddr_storage );
547        s = accept( b, (struct sockaddr *) &sock, &len );
548    }
549    if( s > -1 )
550    {
551        /* "The ss_family field of the sockaddr_storage structure will always
552         * align with the family field of any protocol-specific structure." */ 
553        if( sock.ss_family == AF_INET ) 
554        { 
555            struct sockaddr_in * sock4 = (struct sockaddr_in *)&sock; 
556            addr->type = TR_AF_INET; 
557            addr->addr.addr4.s_addr = sock4->sin_addr.s_addr; 
558            *port = sock4->sin_port; 
559        } 
560        else 
561        { 
562            struct sockaddr_in6 * sock6 = (struct sockaddr_in6 *)&sock; 
563            addr->type = TR_AF_INET6; 
564            addr->addr.addr6 = sock6->sin6_addr;
565            *port = sock6->sin6_port; 
566        } 
567        ++gFd->socketCount;
568    }
569    tr_lockUnlock( gFd->lock );
570
571    return s;
572}
573
574static void
575socketClose( int fd )
576{
577    EVUTIL_CLOSESOCKET( fd );
578}
579
580void
581tr_fdSocketClose( int s )
582{
583    tr_lockLock( gFd->lock );
584
585    if( s >= 0 )
586    {
587        socketClose( s );
588        --gFd->socketCount;
589    }
590
591    assert( gFd->socketCount >= 0 );
592
593    tr_lockUnlock( gFd->lock );
594}
595
596/***
597****
598****  Startup / Shutdown
599****
600***/
601
602void
603tr_fdInit( size_t openFileLimit, size_t socketLimit )
604{
605    int i;
606
607    assert( gFd == NULL );
608    gFd = tr_new0( struct tr_fd_s, 1 );
609    gFd->openFiles = tr_new0( struct tr_openfile, openFileLimit );
610    gFd->openFileLimit = openFileLimit;
611    gFd->lock = tr_lockNew( );
612
613#ifdef HAVE_GETRLIMIT
614    {
615        struct rlimit rlim;
616        getrlimit( RLIMIT_NOFILE, &rlim );
617        rlim.rlim_cur = MIN( rlim.rlim_max,
618                            (rlim_t)( socketLimit + NOFILE_BUFFER ) );
619        setrlimit( RLIMIT_NOFILE, &rlim );
620        gFd->socketLimit = rlim.rlim_cur - NOFILE_BUFFER;
621        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
622    }
623#else
624    gFd->socketLimit = socketLimit;
625#endif
626    tr_dbg( "%zu usable file descriptors", socketLimit );
627
628    for( i = 0; i < gFd->openFileLimit; ++i )
629        gFd->openFiles[i].fd = -1;
630}
631
632void
633tr_fdClose( void )
634{
635    int i = 0;
636
637    for( i = 0; i < gFd->openFileLimit; ++i )
638        if( fileIsOpen( &gFd->openFiles[i] ) )
639            TrCloseFile( i );
640
641    tr_lockFree( gFd->lock );
642
643    tr_free( gFd->openFiles );
644    tr_free( gFd );
645    gFd = NULL;
646}
647
648void
649tr_fdSetPeerLimit( uint16_t n )
650{
651    assert( gFd != NULL && "tr_fdInit() must be called first!" );
652    gFd->socketLimit = n;
653}
654
655uint16_t
656tr_fdGetPeerLimit( void )
657{
658    return gFd ? gFd->socketLimit : -1;
659}
660
Note: See TracBrowser for help on using the repository browser.