github.com/anacrolix/torrent@v1.61.0/test/issue377_test.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  	"sync"
     9  	"testing"
    10  	"testing/iotest"
    11  
    12  	"github.com/anacrolix/log"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/anacrolix/torrent"
    17  	"github.com/anacrolix/torrent/internal/testutil"
    18  	"github.com/anacrolix/torrent/metainfo"
    19  	pp "github.com/anacrolix/torrent/peer_protocol"
    20  	"github.com/anacrolix/torrent/storage"
    21  )
    22  
    23  func justOneNetwork(cc *torrent.ClientConfig) {
    24  	cc.DisableTCP = true
    25  	cc.DisableIPv6 = true
    26  }
    27  
    28  func TestReceiveChunkStorageFailureSeederFastExtensionDisabled(t *testing.T) {
    29  	testReceiveChunkStorageFailure(t, false)
    30  }
    31  
    32  func TestReceiveChunkStorageFailure(t *testing.T) {
    33  	testReceiveChunkStorageFailure(t, true)
    34  }
    35  
    36  func testReceiveChunkStorageFailure(t *testing.T, seederFast bool) {
    37  	seederDataDir, metainfo := testutil.GreetingTestTorrent()
    38  	defer os.RemoveAll(seederDataDir)
    39  	seederClientConfig := torrent.TestingConfig(t)
    40  	seederClientConfig.Debug = true
    41  	justOneNetwork(seederClientConfig)
    42  	seederClientStorage := storage.NewMMap(seederDataDir)
    43  	defer seederClientStorage.Close()
    44  	seederClientConfig.DefaultStorage = seederClientStorage
    45  	seederClientConfig.Seed = true
    46  	seederClientConfig.Debug = true
    47  	seederClientConfig.Extensions.SetBit(pp.ExtensionBitFast, seederFast)
    48  	seederClient, err := torrent.NewClient(seederClientConfig)
    49  	require.NoError(t, err)
    50  	defer seederClient.Close()
    51  	defer testutil.ExportStatusWriter(seederClient, "s", t)()
    52  	leecherClientConfig := torrent.TestingConfig(t)
    53  	leecherClientConfig.Debug = true
    54  	// Don't require fast extension, whether the seeder will provide it or not (so we can test mixed
    55  	// cases).
    56  	leecherClientConfig.MinPeerExtensions.SetBit(pp.ExtensionBitFast, false)
    57  	justOneNetwork(leecherClientConfig)
    58  	leecherClient, err := torrent.NewClient(leecherClientConfig)
    59  	require.NoError(t, err)
    60  	defer leecherClient.Close()
    61  	defer testutil.ExportStatusWriter(leecherClient, "l", t)()
    62  	info, err := metainfo.UnmarshalInfo()
    63  	require.NoError(t, err)
    64  	leecherStorage := diskFullStorage{
    65  		pieces: make([]pieceState, info.NumPieces()),
    66  		data:   make([]byte, info.TotalLength()),
    67  	}
    68  	defer leecherStorage.Close()
    69  	leecherTorrent, new := leecherClient.AddTorrentOpt(torrent.AddTorrentOpts{
    70  		InfoHash: metainfo.HashInfoBytes(),
    71  		Storage:  &leecherStorage,
    72  	})
    73  	leecherStorage.t = leecherTorrent
    74  	assert.True(t, new)
    75  	seederTorrent, err := seederClient.AddTorrent(metainfo)
    76  	require.NoError(t, err)
    77  	// Tell the seeder to find the leecher. Is it guaranteed seeders will always try to do this?
    78  	seederTorrent.AddClientPeer(leecherClient)
    79  	<-leecherTorrent.GotInfo()
    80  	r := leecherTorrent.Files()[0].NewReader()
    81  	defer r.Close()
    82  	// We can't use assertReadAllGreeting here, because the default storage write error handler
    83  	// disables data downloads, which now causes Readers to error when they're blocked.
    84  	if false {
    85  		assertReadAllGreeting(t, leecherTorrent.NewReader())
    86  	} else {
    87  		for func() bool {
    88  			// We don't seem to need to seek, but that's probably just because the storage failure is
    89  			// happening on the first read.
    90  			r.Seek(0, io.SeekStart)
    91  			if err := iotest.TestReader(r, []byte(testutil.GreetingFileContents)); err != nil {
    92  				t.Logf("got error while reading: %v", err)
    93  				return true
    94  			}
    95  			return false
    96  		}() {
    97  		}
    98  	}
    99  	// TODO: Check that PeerConns fastEnabled matches seederFast?
   100  	// select {}
   101  }
   102  
   103  type pieceState struct {
   104  	complete bool
   105  }
   106  
   107  type diskFullStorage struct {
   108  	pieces                        []pieceState
   109  	t                             *torrent.Torrent
   110  	defaultHandledWriteChunkError bool
   111  	data                          []byte
   112  
   113  	mu          sync.Mutex
   114  	diskNotFull bool
   115  }
   116  
   117  func (me *diskFullStorage) Piece(p metainfo.Piece) storage.PieceImpl {
   118  	return pieceImpl{
   119  		mip:             p,
   120  		diskFullStorage: me,
   121  	}
   122  }
   123  
   124  func (me *diskFullStorage) Close() error {
   125  	return nil
   126  }
   127  
   128  func (d *diskFullStorage) OpenTorrent(
   129  	_ context.Context,
   130  	info *metainfo.Info,
   131  	infoHash metainfo.Hash,
   132  ) (storage.TorrentImpl, error) {
   133  	return storage.TorrentImpl{Piece: d.Piece, Close: d.Close}, nil
   134  }
   135  
   136  type pieceImpl struct {
   137  	mip metainfo.Piece
   138  	*diskFullStorage
   139  }
   140  
   141  func (me pieceImpl) state() *pieceState {
   142  	return &me.diskFullStorage.pieces[me.mip.Index()]
   143  }
   144  
   145  func (me pieceImpl) ReadAt(p []byte, off int64) (n int, err error) {
   146  	off += me.mip.Offset()
   147  	return copy(p, me.data[off:]), nil
   148  }
   149  
   150  func (me pieceImpl) WriteAt(p []byte, off int64) (int, error) {
   151  	off += me.mip.Offset()
   152  	if !me.defaultHandledWriteChunkError {
   153  		go func() {
   154  			me.t.SetOnWriteChunkError(func(err error) {
   155  				log.Printf("got write chunk error to custom handler: %v", err)
   156  				me.mu.Lock()
   157  				me.diskNotFull = true
   158  				me.mu.Unlock()
   159  				me.t.AllowDataDownload()
   160  			})
   161  			me.t.AllowDataDownload()
   162  		}()
   163  		me.defaultHandledWriteChunkError = true
   164  	}
   165  	me.mu.Lock()
   166  	defer me.mu.Unlock()
   167  	if me.diskNotFull {
   168  		return copy(me.data[off:], p), nil
   169  	}
   170  	return copy(me.data[off:], p[:1]), errors.New("disk full")
   171  }
   172  
   173  func (me pieceImpl) MarkComplete() error {
   174  	me.state().complete = true
   175  	return nil
   176  }
   177  
   178  func (me pieceImpl) MarkNotComplete() error {
   179  	me.state().complete = false
   180  	return nil
   181  }
   182  
   183  func (me pieceImpl) Completion() storage.Completion {
   184  	return storage.Completion{
   185  		Complete: me.state().complete,
   186  		Ok:       true,
   187  	}
   188  }