莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Kilim的小BUG

          Posted on 2010-11-03 23:22 dennis 閱讀(2806) 評論(2)  編輯  收藏 所屬分類: java

              我最近在實現一個基于Kilim的HttpClient,在處理響應body特別大的情形下遇到了kilim的一個BUG,有必要記錄下。
              問題是這樣,Kilim將連接封裝為EndPoint對象,EndPoint有個方法fill用于從管道讀數據到緩沖區,并且可以指定希望至少讀到多少個字節(atLeastN)才返回。那么在進入此方法的時候會判斷緩沖區是否有足夠空間容納atLeastN個字節,如果沒有,則創建一個更大的緩沖區,并將“老”的緩沖區的數據拷貝到新緩沖區,這部分代碼是這樣實現:
          public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
                  
          if (buf.remaining() < atleastN) {
                      ByteBuffer newbb 
          = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
                      buf.rewind();
                      newbb.put(buf);
                      buf 
          = newbb;
                  }
                  ……
          }

              后面的代碼我省略了,這個BUG就出現在這段代碼里。這段代碼的邏輯很簡單,先是創建一個新的更大的緩沖區,然后將老的緩沖區的數據put到新的緩沖區,在put之前調用rewind方法將老的緩沖區的position設置為0。查看rewind干了什么:
           public final Buffer rewind() {
              position 
          = 0;
              mark 
          = -1;
              
          return this;
              }

              僅僅是將position設置為0,并讓mark失效。position指向下一個讀或者寫的位置,這里在寫入到新緩沖區之前確實需要將position設置為0,以便寫入從老的緩沖區第一個位置開始。問題是什么?問題是position僅僅指定了下一個讀取數據的位置,卻沒有指定有效數據的大小,換句話說,沒有指定老的緩沖區的limit。因此這里造成的后果是老的緩沖區整個被寫入到新的老緩沖區,包括有效數據和無效數據,默認情況下緩沖區的limit等于capacity。

             這個bug可以通過下面程序看出來:
                  ByteBuffer old = ByteBuffer.allocate(8);
                  old.putInt(
          99);
                  ByteBuffer newBuf 
          = ByteBuffer.allocate(16);
                  old.rewind();
                  newBuf.put(old);
                  newBuf.putInt(
          100);

                  newBuf.flip();
                  System.out.println(newBuf.remaining());
                  System.out.println(newBuf.getInt());
                  System.out.println(newBuf.getInt());
                  System.out.println(newBuf.getInt());

              先往old寫入一個整數99,然后創建newBuf并寫入old數據,并再寫入一個整數100,最后從newBuf讀數據。本來我們預期只應該讀到兩個整數99和100,但是中間卻插入一個0,輸出如下:

          12
          99
          0
          100

              12表示緩沖區可讀的數據,本來應該是8個字節,卻多了4個字節的無效數據。

               這個BUG解決很簡單,將rewind修改為flip方法即可,flip不僅將position設置為0,也將limit設置為當前位置:
            public final Buffer flip() {
              limit 
          = position;
              position 
          = 0;
              mark 
          = -1;
              
          return this;
              }


              修改上面的測試程序,符合我們的預期了:
                  ByteBuffer old = ByteBuffer.allocate(8);
                  old.putInt(
          99);
                  ByteBuffer newBuf 
          = ByteBuffer.allocate(16);
                  old.flip();
                  newBuf.put(old);
                  newBuf.putInt(
          100);

                  newBuf.flip();
                  System.out.println(newBuf.remaining());
                  System.out.println(newBuf.getInt());
                  System.out.println(newBuf.getInt());;

              輸出:
          8
          99
          100

              總結,使用rewind的前提是limit已經正確設置,例如你將buffer寫入成功并想記錄這個buffer,可以使用rewind:
          while (buffer.hasRemaining()) //發送數據
              networkChannel.write(buffer);
          buffer.rewind(); 
          // 重置buffer,準備寫入日志管道
          while (buffer.hasRemaining()) // 寫入日志
              loggerChannel.write(buffer);

             而flip用于緩沖區發送或者讀取之前,也就是將緩沖區設置為等待傳出狀態。

          評論

          # re: Kilim的小BUG  回復  更多評論   

          2010-12-14 07:30 by zz69
          這里應該注意到原始設計中 的使用場景是當oldbuffer 不夠大的時候,也就是oldbuffer有一個隱含的意思就是已被寫滿,那么limit == capacity 使用flip抑或是rewind效果是一樣的,甚至這個地方rewind更能表現出代碼原始的使用企圖

          個人理解,還請多多指教

          # re: Kilim的小BUG  回復  更多評論   

          2010-12-14 10:48 by dennis

          @zz69
          這里并沒有什么隱含的意思,判斷條件是
          buf.remaining() < atleastN

          而不是

          !buf.hasRemaining()
          主站蜘蛛池模板: 武强县| 开原市| 博兴县| 营山县| 阿合奇县| 彰武县| 壤塘县| 济南市| 邵阳县| 奇台县| 石家庄市| 舟山市| 慈利县| 聊城市| 隆昌县| 郴州市| 驻马店市| 竹溪县| 锡林浩特市| 定州市| 裕民县| 雅江县| 宝应县| 桓仁| 马关县| 鄂托克前旗| 通辽市| 共和县| 南宁市| 金坛市| 碌曲县| 潢川县| 宁晋县| 枣阳市| 攀枝花市| 英超| 新营市| 永济市| 松溪县| 嘉兴市| 郎溪县|