Retry database write?

Post here your questions about SFS2X. Here we discuss all server-side matters. For client API questions see the dedicated forums.

Moderators: Lapo, Bax

User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Retry database write?

Postby moccha » 08 Jul 2021, 13:32

Hello again, sorry for the frequent messages. This is my last one for a while. :oops:

I run a database update when a player disconnects that saves their data, with this as an example:

Code: Select all

      try
               {
                     // Save player data
                   dbManager.executeUpdate("UPDATE players SET save_data=? WHERE username=?", data);
               }
               catch (SQLException ex)
               {
                   // Database write failed
                   trace(ex.getMessage()+ ", error saving data for "+user.getName());
               } 


However, my worry is what happens if there's a database error or the connection is lost, which can happen. The catch block will output the error, but then all of the user's session data disappears.

Is there a standard way of re-attempting the save the credentials until successful or should I just let it fail? Some ideas that came to mind are running a timer or using a thread.sleep() in a loop, but I don't know how practical or safe that would be, plus handling it happening for thousands of players if the database went down.

I hate the idea of a users data not saving and them losing progress.
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 08 Jul 2021, 14:54

Hi,
Thread.sleep() is a bad idea because you're blocking a thread for a very long time and if multiple threads end up in this situation you're going to kill the server's performance.

Retrying after some time via a Task schedule in the TaskScheduler is definitely the way to go.
The only problem I see with this approach is that you have to keep some kind of state so that you don't end running in an endless loop, where the same error occurs all the times and the task is rescheduled indefinitely.

This is relatively easy to solve though as you can pass a counter to your Task class (which is a Runnable) and set a MAX_RETRY constant somewhere so that you can compare the two and give up at some point.

Cheers
Lapo
--
gotoAndPlay()
...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 09 Jul 2021, 16:05

Thats good idea!

How can I pass a counter to a Task? I have a TaskRunner in my project, but it only loops through and checks room variables for logic.

For another point, the only other problem I would have to consider is that the user could login again before the write completes, which would cause them to overwrite their "saving" data with old data.

I could add the user to a block list and disallow them from logging in again until the data properly saves or max attempts have been reached. Does that sound like a good solution to you?
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 09 Jul 2021, 16:53

Your Task class must have a constructor so that you can pass a counter to it :)

In other words:

Code: Select all

public class MyTask implements Runnable
{
   private int counter;

   public MyTask(int counter)
   {
      this.counter = counter;
   }

   @Override
   public void run()
   {
             //...
   }
}


Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 09 Jul 2021, 20:23

I've gotten the loop working now thanks to your help, but the final part I am stuck on it blocking a user from logging in. If an error is detected, I add the user to a list to temporarily block them from logging in until their data has saved.

I use the LoginAssistantComponent and the preProcessPlugin for user login, following the site tutorial.

My code looks like this:

Code: Select all

@Override
                public void execute(LoginData ld)
                {
                    String clientPass = ld.clientIncomingData.getUtfString("passwd");
                   
                    // Here is the problem
                    if(saveList.contains(ld.userName))
                    {
                        //Block user from logging in?
                    }

                    System.out.println("Client Password: "+clientPass);

                    // Let's see if the password from the DB matches that of the user
                    if (!ld.password.equals(clientPass))
                    {
                        throw new PasswordCheckException();
                    }

                    // Success!
                }


I can't figure out how to throw a general login error for the lac. If I throw a PasswordCheckException, it adds to failed login attempts, which isn't good.
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 10 Jul 2021, 12:43

I can't figure out how to throw a general login error for the lac. If I throw a PasswordCheckException, it adds to failed login attempts, which isn't good.

If the login fails the counter will be increased, there's not much else that can be done.
As regards exceptions there is the SFSLoginException which is the base class for all kinds of errors, and the PasswordCheckException is just a wrapper around it (which ultimately triggers the SFSLoginException).

If these DB errors happen frequently you may need to increase the max number of failed login attempts.

Also as a side note, I don't think these issues should manifest very frequently at all, especially the disconnects which are typically very rare events. I suspect you might be trying to work around database issues that should be investigated further and solved at the the root cause level.

Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 11 Jul 2021, 02:24

To clarify, the user's name would only added to a list when the data does not save correctly. I want to prevent them from logging in if they're found to be on that list, until it times out or saves. This would be in the rare time that a database exception occurred, not something regular.

For another unrelated example, I'd like to be able to allow only specific users to login for testing at certain times. Is there a way to throw a generic login error (or stop the login) using the lac as in the example? I know in the other login you could throw something like a SFSLoginException.
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 12 Jul 2021, 08:14

I think you can throw an SFSLoginException from within the preProcess handler, using the Login Component.

Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 12 Jul 2021, 14:12

I tried throwing an error when logging in, but the catch triggers with a blank message for "ex".

Code: Select all

 lac.getConfig().preProcessPlugin = new ILoginAssistantPlugin()
            {
                @Override
                public void execute(LoginData ld)
                {
                    String clientPass = ld.clientIncomingData.getUtfString("passwd");
                   
                    if(saveList.contains(ld.userName))
                    {
                        SFSErrorData errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME);
                       
                        //Block user from logging in
                        try
                        {
                            trace("Throwing error...");
                            throw new SFSLoginException("", errData);
                        }
                        catch (SFSLoginException ex)
                        {
                            trace(ex+", Error occurred when blocking login for "+ld.userName);
                        }
                    }

                    // Let's see if the password from the DB matches that of the user
                    if (!ld.password.equals(clientPass))
                    {
                        throw new PasswordCheckException();
                    }

                    // Success!
                }
            };
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 12 Jul 2021, 15:14

You should not try/catch the SFSLoginException though. If you do so you're not stopping the flow of the login procedure which will just keep going. The point of this exception is to signal a login error and stop the procedure.

Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 12 Jul 2021, 15:36

I cannot use a throw new SFSLoginException because the compiler complains "unreported exception SFSLoginException; must be caught or declared to be thrown". I don't think the preProcess looks for an exception to be thrown.

Edit: Can I do this? :

Code: Select all

lac.getConfig().preProcessPlugin = new ILoginAssistantPlugin()
            {
                @Override
                public void execute(LoginData ld) throws SFSLoginException
                {
                    String clientPass = ld.clientIncomingData.getUtfString("passwd");
                   
                    if(saveList.contains(ld.userName))
                    {
                        SFSErrorData errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME);
                       
                        //Block user from logging in
                       throw new SFSLoginException("", errData);
                    }
...


The difference being "public void execute(LoginData ld) throws SFSLoginException". I just tried that and it works, but I didn't know I could extend the function like that. What is it called in Java when you add an additional "throws" parameter like that?
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 13 Jul 2021, 09:17

I cannot use a throw new SFSLoginException because the compiler complains "unreported exception SFSLoginException; must be caught or declared to be thrown".

My bad, I should have checked.
In any case you have the PasswordCheckException available to stop the Login flow.

I just tried that and it works, but I didn't know I could extend the function like that. What is it called in Java when you add an additional "throws" parameter like that?

Hi, that seems weird. When you are implementing a specific interface and you mark a method as @Override, you can't add a throw clause if the method from the interface doesn't declare one.

My memory on this was a bit fuzzy so I ran a quick test implementing the run() method from the Runnable interface, and adding a throws clause. The compiler didn't like it.

Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games
User avatar
moccha
Posts: 112
Joined: 13 Feb 2014, 16:09

Re: Retry database write?

Postby moccha » 14 Jul 2021, 14:16

That's really weird then, because my compiler doesn't complain and it also works without any problems. I wonder what's going on there? Maybe there's something different with the lac execute() function.

Are you using Java 11?

Update - http://docs2x.smartfoxserver.com/api-do ... lugin.html

It looks like execute() throws a java.lang.Exception, and in Java you can throw a more specific exception or none at all, but never something more generic. Overriding with SFSLoginException works because it's a more specific error.
User avatar
Lapo
Site Admin
Posts: 22999
Joined: 21 Mar 2005, 09:50
Location: Italy

Re: Retry database write?

Postby Lapo » 15 Jul 2021, 14:46

moccha wrote:It looks like execute() throws a java.lang.Exception, and in Java you can throw a more specific exception or none at all, but never something more generic. Overriding with SFSLoginException works because it's a more specific error.

Indeed, that's the catch :)
The method signature already throws the most generic type of checked exceptions.

Cheers
Lapo

--

gotoAndPlay()

...addicted to flash games

Return to “SFS2X Questions”

Who is online

Users browsing this forum: No registered users and 37 guests