<groupId>org.apache.commons</groupId> <artifactId>commons-vfs2</artifactId> <version>2.2</version>
<groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version>
使用SFTP上传文件到Windows系统上的SFTP服务器的过程中,先使用临时文件名,上传成功之后使用moveTo方法将文件名改为最终文件名
使用moveTo方法改名的过程中,如果是使用Windows10自带的OpenSSH服务器,会直接抛出一个异常,异常信息为无法获取当前用户的组ID
查看相应的栈信息时发现是执行moveTo方法时,先通过获取文件用户组以及用户来判断是否拥有写权限,使用了ChannelExec发送id -G或者id -u命令。
通过上述步骤得知,通过ChannelExec和id -G命令无法得到文件所属的用户组,所以抛出了找不到用户组的异常
通过在网上查找资料,发现commons-vfs2在后续的版本中对此进行了优化,如果是不支持ChannelExec的系统,就默认返回一个值
判断文件系统是否支持Exec,不支持就返回默认值,支持才回去获取文件所属用户组
通过上面图片可以看出,execDisabled字段是在构造SftpFileSystem时提前进行一次获取用户ID的操作,如果出现异常,表明文件系统不支持Exec的操作,execDisabled字段就设为true,在后续就不会再去获取文件所属用户组以及用户
使用moveTo方法改名的过程中,如果是使用FreeSSH软件搭建的SSH服务器,会在moveTo方法中卡住,一直不会退出。
通过Debug发现最终依然是在获取用户组的方法中出现的问题。一次Debug下去发现是通过ChannelExec发送了id -G的命令之后,在使用PipedInputStream读取返回的数据时死锁。
PipedInputStream是Java中的管道流,和PipedOutputStream配套使用,两边都能对同一个缓冲区进行操作,PipedOutputStream往里面进行写入数据,PipedInputStream从其中读取数据。
in字段表示从连接的管道输出流接收到的下一个数据字节将存储在循环缓冲区中的位置索引。 in<0表示缓冲区为空, in==out表示缓冲区已满。writeSide字段是表示PipedOutputStream所在线程,closedByWriter代表Writer是否已经关闭。
从上述字段来分析,在while循环中发生了死锁,代表in是一直小于0,且并没有被PipedOutputStream关闭,而如果writeSide如果存在,只需要循环两次就会退出,并不会造成死锁。
通过writeSide赋值操作发现是在PipedInputStream流中的receive方法中得到当前线程,而receive方法是在PipedOutputStream流的wirite方法中调用。如果说writeSide字段为null,表示PipedOutputStream流并没有调用write方法往缓冲区中写入数据
通过上述分析发现,往FreeSSH软件搭建的SFTP服务器上传文件,且使用moveTo方法改名死锁的原因很有可能是通过Exec获取文件所属的用户组,但是SFTP服务器并没有返回任何数据,导致当前PipedInputStream流一直等待PipedOutputStream流返回数据并且关闭