title: Thread safety with Netty and Bukkit
body: <p>I'm creating listeners for when a channel is read, and when a channel writes in Netty. Listeners are handled in the Bukkit's event system, that's not the problem, the problem is thread safety. The Bukkit Api is run on the main thread and must be in sync or basically the server will explode. Netty is run on multiple threads, so cross communication can be hard with the Bukkit api. I asked on the Bukkit forums and the best answer i got was to create an AtomicBoolean and have a while loop, this would solve the problem with syncing the cancellation of the sending and writing packets but it doesn't fix the problem of changing the packet that is being sent /written. when an event is called in Bukkit the listeners are called from each class, under @EventHandler. all of that code must be in sync with the main thread, I'm not to sure how i would use synchronized in this scenario. sorry for the bad code formatting i cant work this system very well.</p>
  
  <pre><code>public class ConnectionInjector extends ChannelDuplexHandler {
  
      private User user;
      private Channel channel;
      private PacketRecieveEvent recieve;
      private PacketSendEvent send;
      private boolean isInjected = false;
      private boolean isClosed = false;
  
      public ConnectionInjector(User user) {
          this.init(user.getPlayer());
      }
      public void close() {
          if (!this.isClosed) {
              this.isClosed = true;
              if (this.isInjected) {
                  getChannel().eventLoop().submit(new Callable&lt;Object&gt;() {
                      @Override
                      public Object call() throws Exception {
  
                          getChannel().pipeline().remove(ConnectionInjector.this);
                          return null;
  
                      }
  
                  });
  
                  this.isInjected = false;
              }
          }
      }
  
      public boolean isInjected() {
          return this.isInjected;
      }
  
      public boolean isClosed() {
          return this.isClosed;
      }
  
  
      @Override
      public void write(ChannelHandlerContext context, Object packet, ChannelPromise channel) {
          if (this.isClosed()){
              throw new IllegalStateException("Connection closed already");
          }
          PacketData d = new PacketData(packet);
          send = new PacketSendEvent(user, d);//the event
          Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), new Runnable() {
              public void run() {
                  Bukkit.getPluginManager().callEvent(send); //runnable puts it on the main thread
              }
          });//end of bukkit thread back on netty
          if (!send.isCancelled()){// this possible happens before the above code which is very bad
              try {
                  if (send.getPacket().getRawPacket() == null){
                      throw new IllegalStateException("sent packet was null: " + send.getPacket().getRawPacket().getClass().getName());
                  }
                  super.write(context, send.getPacket().getRawPacket(), channel); //possible happens before send is even created.
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
  
      @Override
      public void channelRead(ChannelHandlerContext context, final Object packet) throws Exception {
          PacketData p = new PacketData(packet);
          recieve = new PacketRecieveEvent(user, p);
          Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), new Runnable() {
              public void run() {
                  Bukkit.getPluginManager().callEvent(recieve); 
              }
  
          });
          if (recieve.isCancelled()){
              super.channelRead(context, recieve.getPacket().getRawPacket());
          }
  
  
      }
  
      public void injectfakePacket(Object packet) {
          if (this.isClosed()){
              throw new IllegalStateException("Injector is closed");
          }
          this.getChannel().pipeline().context("encoder").fireChannelRead(packet);
      }
  
      public void init(Player player) {
        this.channel = (Channel) Nms.getNetChannel(player);
          this.channel.pipeline().addBefore("packet_handler", "epickitpvp", this);
          this.isInjected = true;
      }
  
      public Channel getChannel() {
          if (this.channel == null){
              throw new IllegalStateException("channel is null");
          }
          return this.channel;
      }
  } 
  </code></pre>