How to do a database lock in AppEngine (GAE)? - google-app-engine

In GAE, I've got a table full of "one offs" -- things like "last-used sequence number" and the like that don't really fall into other tables. It's a simple String-key with String-value pair.
I've got some code to grab a named integer and increment it, like so:
#PersistenceCapable(detachable="true")
public class OneOff
{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String dataKey;
#Persistent
private String value;
public OneOff(String kk, String vv)
{
this.dataKey = kk;
this.value = vv;
}
public static OneOff persistOneOff(String kk, String vv)
{
OneOff oneoff= new OneOff(kk, vv);
PersistenceManager pm = PMF.get().getPersistenceManager();
try
{
pm.makePersistent(oneoff);
}
finally
{
pm.close();
}
return oneoff;
}
// snip...
#SuppressWarnings("unchecked")
synchronized
public static int getIntValueForKeyAndIncrement(String kk, int deFltValue)
{
int result = 0;
OneOff oneOff = null;
PersistenceManager pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery(OneOff.class);
query.setFilter("dataKey == kkParam");
query.declareParameters("String kkParam");
List<OneOff> oneOffs = (List<OneOff>) query.execute(kk);
int count = oneOffs.size();
if (count == 1)
{
oneOff = oneOffs.get(0);
result = Integer.parseInt(oneOff.value);
}
else if (count == 0)
{
oneOff = new OneOff(kk, "default");
result = deFltValue;
}
else
{
// Log WTF error.
}
// update object in DB.
oneOff.value = "" + (result+1);
try
{
pm.makePersistent(oneOff);
}
finally
{
pm.close();
}
return result;
}
// etc...
However, when I make these calls:
int val1 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
int val2 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
int val3 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
Sometimes I get the desired increment and sometimes I get the same value. It appears that my DB access is running asynchronously, when I'd like to lock the DB for this particular transaction.
I thought that
synchronized
public static
was supposed to do that for me, but apparently not (probably due to multiple instances running!)
At any rate -- how do I do the thing that I want? (I want to lock my DB while I get & update this value, to make the whole thing concurrency-safe.)
Thanks!
== EDIT ==
I have accepted Robert's as the correct answer, since transactions were, indeed, what I wanted. However, for completeness, I have added my updated code below. I think it's correct, although I'm not sure about the if(oneOff==null) clause (the try-catch bit.)
public static int getIntValueForKeyAndIncrement(String kk, int defltValue)
{
int result = 0;
Entity oneOff = null;
int retries = 3;
// Using Datastore Transactions
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
while (true)
{
com.google.appengine.api.datastore.Transaction txn = datastore.beginTransaction();
try
{
Key oneOffKey = KeyFactory.createKey("OneOff", kk);
oneOff = datastore.get (oneOffKey);
result = Integer.parseInt((String) oneOff.getProperty("value"));
oneOff.setProperty("value", "" + (result+1));
datastore.put(oneOff);
txn.commit();
break;
}
catch (EntityNotFoundException ex)
{
result = defltValue;
}
catch (ConcurrentModificationException ex)
{
if (--retries < 0)
{
throw ex;
}
}
if (oneOff == null)
{
try
{
Key oneOffKey = KeyFactory.createKey("OneOff", kk);
oneOff = new Entity(oneOffKey);
oneOff.setProperty("value", "" + (defltValue+1));
datastore.put(txn, oneOff);
datastore.put(oneOff);
txn.commit();
break;
}
finally
{
if (txn.isActive())
{
txn.rollback();
}
}
}
else
{
if (txn.isActive())
{
txn.rollback();
}
}
}
return result;
}

You should be updating your values inside a transaction. App Engine's transactions will prevent two updates from overwriting each other as long as your read and write are within a single transaction. Be sure to pay attention to the discussion about entity groups.

Related

Salesforce Apex class to sort years and year ranges giving incorrect output

Tried writing a sort method where input was given like a string of comma-delimited years and year ranges String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
Expectation is to get a string of comma-delimited years and year ranges,and remove all duplicates and invalid inputs.
Below is class written which is not giving me correct output
public class sortYearAndYearRangesString {
public static List<String> sortSpecialString(String input) {
system.debug(input);
List<String> inputList = input.split('');
system.debug(inputList);
Map<Integer,String> stringMap = new Map<Integer,String>();
system.debug(stringMap);
List<String> output = new List<String>();
for (Integer i=0; i<inputList.size(); i++) {
String charac = inputList[i];
if(!charac.isAlphaNumeric()) {
system.debug(charac);
stringMap.put(i,charac);
}else {
output.add(charac);
system.debug(output);
}
}
String finalString = String.join(output,'');
system.debug(finalString);
List<String> resultList = finalString.reverse().split('');
for( Integer I : stringMap.keySet() ){
system.debug(I);
resultList.add(I,stringMap.get(I));
system.debug(resultList);
}
return resultList;
}
Tried validating the solution in Anonymous Apex but no success
public static void validateSolution() {
String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
List<Integer> expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025};
List<Integer> actual = sortYearAndYearRangesString(input);
System.assertEquals(expected, actual, 'Invalid Results');
}
}
Your help is appreciated
Regards
Carolyn
According to your test case, you should also define at least a constant for a maximum value, in order to exclude 20261. Probably you need a minimum too.
I used 1700 as min and 4000 as max because these are the limits for a Date or Datatime field: docs
Moreover the method must return a List<Integer> instead of a List<String>.
You don't need a Map, just a Set would work.
public class SortYearAndYearRangesString {
private static final Integer MAX_YEAR = 4000;
private static final Integer MIN_YEAR = 1700;
public static List<Integer> sortSpecialString(String input) {
Set<Integer> output = new Set<Integer>();
List<String> yearsList = input.split(',');
for (String yearString : yearsList) {
yearString = yearString.trim();
if (yearString.isNumeric()) {
try {
Integer year = Integer.valueOf(yearString);
if (year >= MIN_YEAR && year <= MAX_YEAR) {
output.add(year);
}
} catch (TypeException e) {
System.debug(e.getMessage());
}
} else {
List<String> range = yearString.split('-');
if (range.size() == 2 && range[0].isNumeric() && range[1].isNumeric()) {
try {
// Modify the following two lines once you know how to handle range like 1300-1500 or 3950-4150
Integer firstYear = Math.max(Integer.valueOf(range[0]), MIN_YEAR);
Integer lastYear = Math.min(Integer.valueOf(range[1]), MAX_YEAR);
while (firstYear <= lastYear) {
output.add(firstYear++);
}
} catch (TypeException e) {
System.debug(e.getMessage());
}
}
}
}
List<Integer> sortedYears = new List<Integer>(output);
sortedYears.sort();
return sortedYears;
}
}
If a range that exceed the boundaries (like 1300-1500 or 3950-4150) should be treated as invalid and skipped, please change these lines
Integer firstYear = Math.max(Integer.valueOf(range[0]), MIN_YEAR);
Integer lastYear = Math.min(Integer.valueOf(range[1]), MAX_YEAR);
while (firstYear <= lastYear) {
output.add(firstYear++);
}
as follow:
Integer firstYear = Integer.valueOf(range[0]);
Integer lastYear = Integer.valueOf(range[1]);
if (firstYear >= MIN_YEAR && lastYear <= MAX_YEAR) {
while (firstYear <= lastYear) {
output.add(firstYear++);
}
}
I tested it in anonymous console with the following code:
String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
List<Integer> expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025};
List<Integer> actual = SortYearAndYearRangesString.sortSpecialString(input);
System.debug(actual);
System.assertEquals(expected, actual, 'Invalid Results');
input = '1500,2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013,3998-4002';
expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025,3998,3999,4000};
actual = SortYearAndYearRangesString.sortSpecialString(input);
System.assertEquals(expected, actual, 'Invalid Results');
I made some changes to the class.
It does increment all ranges - and doesn't check if they're years that would make sense. You'll need to add that logic in there (e.g. 1500-1600 would return all years between 1500-1600. Prob best to cap at 1900 or something)
public class SortYearAndYearRangesString{
public static List<Integer> sortSpecialString(String input){
List<String> inputList = input.split(',');
Set<Integer> output = new Set<Integer>();
system.debug('input ' + input);
system.debug('inputList ' + inputList);
for (String s : inputList){
Set<Integer> tempSet = new Set<Integer>();
s.remove(' ');
if (s.contains('-')){
//// break the ranges and fill in years
List<String> tempSet2 = s.split('-');
for (String s2 : tempSet2){
try{
///capture valid integers
Integer tempInt = Integer.valueOf(s2);
tempSet.add(tempInt);
} catch (Exception e){
tempSet.clear();
break;
}
}
System.debug('set ' + tempSet);
if (tempSet.size() > 1){
List<Integer> tempList = new List<Integer>(tempSet);
tempList.sort ();
Integer r = tempList.size() - 1;
// iterate through the years
for (Integer i = tempList.get(0); i < tempList.get(r); i++){
tempSet.add(i) ;
}
}
} else{
try{
///capture valid integers
Integer tempInt = Integer.valueOf(s);
tempSet.add(tempInt);
} catch (Exception e){
continue;
}
}
output.addAll(tempSet);
}
// output is currently set of ints, need to convert to list of integer
List<Integer> finalOutput = new List<Integer>(output);
finalOutput.sort ();
System.debug('finalOutput :' + finalOutput);
return finalOutput;
}}

Discord Bot (JDA) is not modifying my Roles

I Programmed an Discord Bot to make an Time Role (a Role that Changes its name every second to: "{UserName} --D --H --min --sec).
It worked Fine, but after an hour it does not worked anymore, but I did not change anything in the Code.
I have an Command ("{prefix} shutdown"), it basicly do ShardManager.shutdown(); and than I get an error.
Code for the Command:
package Discord.Commands.TimeRank;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
import Discord.Bot;
import Discord.Commands.Command;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
public class startTimeRole extends Command {
public static Server server;
public Timer timer;
public static boolean isRunning = false;
#Override
public void set(String prefix, String help) {
prefix = "timeRole";
help = Bot.prefix + " timeRole <STATE> -> STATE = start, pause";
super.set(prefix, help);
}
#Override
public void Execute(MessageReceivedEvent event, String[] args) {
super.Execute(event, args);
server = server.get(event.getGuild());
if (args.length <= 1) {
event.getTextChannel().sendMessage("Please give at least one **Argument**!").queue();
help(event);
return;
} else {
if (args[1].equals("start")) {
if (!isRunning) {
timer = new Timer();
event.getTextChannel().sendMessage("Activated timeRole!").queue();
isRunning = true;
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
for (Client client : server.onlineClients) {
Role timeRole = null;
for (Role role : client.member.getRoles()) {
if (timeRole != null) {
break;
}
if (role.getName().startsWith(client.member.getUser().getName())) {
client.onlineTime.setTime(role.getName().substring(client.member.getUser().getName().length() + 1));
client.onlineTime.plus(10);
timeRole = role;
}
}
if (timeRole != null) {
timeRole.getManager()
.setName(client.member.getUser().getName() + " " + client.onlineTime.get())
.queue();
} else {
try {
server.guild
.addRoleToMember(client.member,
server.guild.createRole()
.setName(client.member.getUser().getName() + " "
+ client.onlineTime.get())
.complete(true))
.queue();
} catch (RateLimitedException e) {
e.printStackTrace();
}
}
}
}
}, 0, 10000);
return;
} else {
event.getTextChannel().sendMessage("timeRole is Active!").queue();
help(event);
return;
}
} else if (args[1].equals("pause")) {
if (isRunning) {
timer.cancel();
event.getTextChannel().sendMessage("Paused timeRole").queue();
;
isRunning = false;
} else {
event.getTextChannel().sendMessage("timeRole is Paused!").queue();
help(event);
return;
}
return;
} else {
event.getTextChannel().sendMessage("Please give one **Argument**").queue();
help(event);
return;
}
}
}
}
Code for the "Server" Class:
package Discord.Commands.TimeRank;
import java.util.ArrayList;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
public class Server {
public ArrayList<Client> clients = new ArrayList<Client>();
public ArrayList<Client> onlineClients = new ArrayList<Client>();
public Guild guild;
private Server(Guild guild) {
this.guild = guild;
}
public static Server get(Guild guild) {
Server server = new Server(guild);
server.clients = server.getMembersAsClients();
server.onlineClients = server.getOnlineMembersAsClients();
return server;
}
public ArrayList<Client> getMembersAsClients() {
ArrayList<Client> clients = new ArrayList<Client>();
System.out.println(guild.getMembers().size());
for (Member member : guild.getMembers()) {
if (!member.getUser().isBot()) {
clients.add(new Client(member));
}
}
return clients;
}
public ArrayList<Client> getOnlineMembersAsClients() {
ArrayList<Client> onlineClients = new ArrayList<Client>();
for (Client client : clients) {
if (!client.member.getUser().isBot()) {
if (client.member.getOnlineStatus() == OnlineStatus.ONLINE) {
onlineClients.add(client);
}
}
}
return onlineClients;
}
}
and Time Class:
package Discord.Commands.TimeRank;
public class Time {
public int Days, Hours, Minutes, Seconds;
public Time() {
}
public String get() {
return Days + "D " + Hours + "H " + Minutes + "min " + Seconds + "sec ";
}
public void plus(int plus) {
Seconds += plus;
if (Seconds >= 60) {
Seconds = 0;
Minutes++;
if (Minutes >= 60) {
Minutes = 0;
Hours++;
if (Hours >= 24) {
Hours = 0;
Days++;
return;
}
}
}
}
public void setTime(String Time) {
String[] Time_ = Time.split(" ");
int Days = Integer.parseInt(Time_[0].split("D")[0]);
int Hours = Integer.parseInt(Time_[1].split("H")[0]);
int Minutes = Integer.parseInt(Time_[2].split("min")[0]);
int Seconds = Integer.parseInt(Time_[3].split("sec")[0]);
this.Days = Days;
this.Hours = Hours;
this.Minutes = Minutes;
this.Seconds = Seconds;
}
}
And the Error:
[DefaultShardManager] INFO RateLimiter - Waiting for 1 bucket(s) to finish. Average queue size of 2 requests
Exception in thread "Timer-0" java.util.concurrent.RejectedExecutionException: The Requester has been stopped! No new requests can be requested!
at net.dv8tion.jda.internal.requests.Requester.request(Requester.java:107)
at net.dv8tion.jda.internal.requests.RestActionImpl.queue(RestActionImpl.java:189)
at net.dv8tion.jda.internal.managers.ManagerBase.queue(ManagerBase.java:121)
at net.dv8tion.jda.api.requests.RestAction.queue(RestAction.java:411)
at net.dv8tion.jda.api.requests.RestAction.queue(RestAction.java:377)
at Discord.Commands.TimeRank.startTimeRole$1.run(startTimeRole.java:67)
at java.util.TimerThread.mainLoop(Unknown Source)
at java.util.TimerThread.run(Unknown Source)
Sorry for that much Code but I just thoght that you could need it to know what, what is.
If you have some questions Just ask them. :D
You are being rate limited. Different requests to the api have different limits on how often you are allowed to perform them. When spamming a request you can easily hit such a limit.
Switching the bot works because every new bot starts at zero until you hit the limit again.
My advice is to decrease the number of requests. Trying to circumvent the limit it therefore abusing the api is against TOS.

RocksDB JNI Slow Read Performance

I am using RocksDB JNI and I found that reads are getting exponentially slow for this program
Initially, it's in an acceptable range. But when the program runs so that the total records reach 1 million then the read time logger prints are showing around 200-300 ms. Getting still worse, as the program runs. Am I using the JNI wrong?
long n = 0;
int counter = 0;
try(
final Options options = new Options()
.setCreateIfMissing(true)
.setComparator(new Comparator(new ComparatorOptions()){
#Override
public String name() {
return "default";
}
#Override
public int compare(final Slice a, final Slice b) {
long x = ByteBuffer.wrap(a.data()).getLong();
long y = ByteBuffer.wrap(b.data()).getLong();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
})
.setWriteBufferSize(64 * SizeUnit.MB)
.setMaxWriteBufferNumber(6)
.setMinWriteBufferNumberToMerge(2);
final RocksDB db = RocksDB.open(options, "/PathToDB/")){
boolean loop = true;
while(loop) {
if(n == Long.MAX_VALUE){
loop = false;
}
for (int j=0;j<4;j++){
try(WriteBatch writeBatch = new WriteBatch()) {
for (int i = 0; i < 200; i++) {
String urlStr = "dummy"+counter;
counter++;
Long score = getScore(urlStr);
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
buf.putLong(score);
writeBatch.put( buf.array() , urlStr.getBytes(UTF_8));
}
long st = System.currentTimeMillis();
db.write(new WriteOptions(), writeBatch);
long et = System.currentTimeMillis();
logger.logMessage(Test.class.getName(), Level.INFO, "RocksDB write of 200 URLs successful. Time taken - {0}", new Object[]{ et-st});
} catch (RocksDBException ex) {
}
}
byte[] firstKey = null, lastKey = null;
int readC = 0;
long st = System.currentTimeMillis();
final RocksIterator it = db.newIterator();
it.seekToFirst();
while(it.isValid() && readC < 50){
lastKey = it.key();
if(firstKey == null){
firstKey = lastKey;
}
it.next();
readC++;
}
long et = System.currentTimeMillis();
logger.logMessage(Test.class.getName(), Level.INFO, "RocksDB read of 50 URLs successful. Time taken - {0}", new Object[]{ et-st});
if(lastKey != null){
db.deleteRange(firstKey, lastKey);
}
n++;
}
}catch (Exception e){
logger.logMessage(Level.SEVERE, Test.class.getName(), e);
}

store data in array from another class method

i have one method in class call Class1 like
' public void getEventFromUser() {
int event;
Scanner input = new Scanner(System.in);
date.getDateFromUser();
// od.inputday();
// od.inputyear();
time.getTimeFromUser();
System.out.println("Enter description :");
description = input.nextLine();
}'
and i want to execute this method and store in array in another class
like
public void addEvent() {
if (numEvent == maxEvent) {
System.out.println("error…no more room to add events");
} else {
schedule[numEvent]=getEventFromUser();
int count = 0;
while (count < numEvent - 1) {
if (schedule[numEvent].isEqual(schedule[count])) {
isFound = true;
break;
}
count++;
if (isFound == true) {
System.out.println("Event already exists-notadding");
schedule[numEvent] = null;
} else {
schedule[numEvent].setDate();
schedule[numEvent].setTime();
schedule[numEvent].setDescription();
numEvent++;
//schedule[numEvent]=schedule[numEvent].getEventFromUser();
}
}
}
} '
so,how can i do this?
pls give me some solution
getEventFromUser() doesn't return a value, which is why schedule[numEvent]=schedule[numEvent].getEventFromUser() is giving you trouble.
Without knowing a bit more about what you're trying to do, it's hard to say if you should have getEventFromUser() return a value or have getEventFromUser() directly store a value in a field in the class. (I'm guessing the setDate, setTime and setDescription methods do this.)

Linq entityref assignment in one to many relation

i have some problem with my linq database with a one to many relation. Here are classes:
private EntityRef<Account> _account;
[Association(Storage = "_account", ThisKey = "IDMovement", OtherKey = "IDAccount", IsForeignKey = true)]
public Account Account
{
get { return this._account.Entity; }
set
{
Account previousValue = this._account.Entity;
if (((previousValue != value) || (this._account.HasLoadedOrAssignedValue == false)))
{
NotifyPropertyChanging("Account");
if ((previousValue != null))
{
this._account.Entity = null;
previousValue.Movements.Remove(this);
}
this._account.Entity = value;
if ((value != null))
{
value.Movements.Add(this);
}
NotifyPropertyChanged("Account");
}
}
}
and on the other side:
private EntitySet<Movement> _movements;
[Association(Storage = "_movements", OtherKey = "IDMovement", ThisKey = "IDAccount")]
public EntitySet<Movement> Movements
{
get { return this._movements; }
set { this._movements.Assign(value); }
}
private void OnMovementAdded(Movement movement)
{
NotifyPropertyChanging("Movement");
movement.Account = this;
NotifyPropertyChanged("Movement");
}
private void OnMovementRemoved(Movement movement)
{
NotifyPropertyChanging("Movement");
movement.Account = null;
NotifyPropertyChanged("Movement");
}
my problem is that when i have to insert new movements in the database i don't know how to assign the "EntityRef _account" variable with a value that i already have in the database.
For example: i have to insert a movement for the account "general", this account is already in my database of course...how can i say to my movement that the account "general" that it has in "movement.Account" it's exactly the same included in my database?
I tried something like that but It doesn't work because it's like i have created a new account:
Movement movement = new Movement();
movement.Category = (from f in context.Categories
where f.Name == this.Categories.ElementAt(this.ChoosenCategory).Name
select f).FirstOrDefault();
movement.Account = (from f in context.Accounts
where f.Name == ChoosenAccount.Name
select f).FirstOrDefault();
context.Movements.InsertOnSubmit(movement);
context.SubmitChanges();
You need to call Load() method for EntityRef such as if (!order.CustomersReference.IsLoaded)
order.CustomersReference.Load();

Resources