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 }